Bug 1660798 - Part 2 - Convert uses of JSAtom* and PropertyName* to ParserAtomId and ParserNameId. r=tcampbell
☠☠ backed out by 1a89b886cba6 ☠ ☠
authorKannan Vijayan <kvijayan@mozilla.com>
Mon, 24 Aug 2020 19:50:43 +0000
changeset 611151 846f88debca60ea217616fdc792a1585aa98f40f
parent 611150 12d099efac67cb345747abd59d4ddf385d8457c4
child 611152 486321748d937d60faa08c2813e424f1da7356e2
push id13943
push userffxbld-merge
push dateMon, 21 Sep 2020 13:41:08 +0000
treeherdermozilla-beta@acc3d41c2c93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1660798
milestone81.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 1660798 - Part 2 - Convert uses of JSAtom* and PropertyName* to ParserAtomId and ParserNameId. r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D82826
js/moz.configure
js/src/builtin/ModuleObject.cpp
js/src/builtin/ReflectParse.cpp
js/src/frontend/BytecodeCompiler.h
js/src/frontend/BytecodeControlStructures.cpp
js/src/frontend/BytecodeControlStructures.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeSection.cpp
js/src/frontend/BytecodeSection.h
js/src/frontend/CallOrNewEmitter.cpp
js/src/frontend/CallOrNewEmitter.h
js/src/frontend/EmitterScope.cpp
js/src/frontend/EmitterScope.h
js/src/frontend/FoldConstants.cpp
js/src/frontend/Frontend2.cpp
js/src/frontend/FullParseHandler.h
js/src/frontend/FunctionEmitter.cpp
js/src/frontend/FunctionEmitter.h
js/src/frontend/LabelEmitter.cpp
js/src/frontend/LabelEmitter.h
js/src/frontend/LexicalScopeEmitter.cpp
js/src/frontend/LexicalScopeEmitter.h
js/src/frontend/ModuleSharedContext.h
js/src/frontend/NameAnalysisTypes.h
js/src/frontend/NameCollections.h
js/src/frontend/NameFunctions.cpp
js/src/frontend/NameFunctions.h
js/src/frontend/NameOpEmitter.cpp
js/src/frontend/NameOpEmitter.h
js/src/frontend/ObjLiteral.cpp
js/src/frontend/ObjLiteral.h
js/src/frontend/ObjectEmitter.cpp
js/src/frontend/ObjectEmitter.h
js/src/frontend/ParseContext-inl.h
js/src/frontend/ParseContext.cpp
js/src/frontend/ParseContext.h
js/src/frontend/ParseNode.cpp
js/src/frontend/ParseNode.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/ParserAtom.cpp
js/src/frontend/ParserAtom.h
js/src/frontend/PropOpEmitter.cpp
js/src/frontend/PropOpEmitter.h
js/src/frontend/SharedContext-inl.h
js/src/frontend/SharedContext.cpp
js/src/frontend/SharedContext.h
js/src/frontend/Stencil.cpp
js/src/frontend/Stencil.h
js/src/frontend/SwitchEmitter.cpp
js/src/frontend/SwitchEmitter.h
js/src/frontend/SyntaxParseHandler.h
js/src/frontend/TDZCheckCache.cpp
js/src/frontend/TDZCheckCache.h
js/src/frontend/Token.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/frontend/UsedNameTracker.h
js/src/jsnum.cpp
js/src/jsnum.h
js/src/util/StringBuffer.cpp
js/src/util/StringBuffer.h
js/src/vm/BuiltinObjectKind.cpp
js/src/vm/BuiltinObjectKind.h
js/src/vm/GeneratorObject.cpp
js/src/vm/GeneratorObject.h
js/src/vm/Instrumentation.cpp
js/src/vm/Instrumentation.h
js/src/vm/JSContext.h
js/src/vm/ModuleBuilder.h
js/src/vm/Printer.cpp
js/src/vm/Printer.h
js/src/vm/Scope.cpp
js/src/vm/Scope.h
js/src/vm/SelfHosting.cpp
js/src/vm/SelfHosting.h
js/src/vm/StringType.cpp
js/src/vm/StringType.h
js/src/wasm/AsmJS.cpp
js/src/wasm/AsmJS.h
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -73,22 +73,16 @@ def js_disable_shell(value):
 
 set_config('JS_DISABLE_SHELL', js_disable_shell)
 
 set_define('JS_64BIT', depends(target)(lambda t: t.bitness == 64 or None))
 
 set_define('JS_PUNBOX64', depends(target)(lambda t: t.bitness == 64 or None))
 set_define('JS_NUNBOX32', depends(target)(lambda t: t.bitness == 32 or None))
 
-# Bits of Stencil-related parser-atoms work are being landed before
-# being enabled.  This define controls that code, and will be removed,
-# along with guard code in ParserAtoms.cpp, when the final transition
-# to parser atoms lands.
-set_define('JS_PARSER_ATOMS', None)
-
 
 # SpiderMonkey as a shared library, and how its symbols are exported
 # ==================================================================
 js_option('--disable-shared-js', when=js_standalone,
           help='{Create|Do not create} a shared library')
 
 js_option('--disable-export-js', when=js_standalone,
           help='{Mark|Do not mark} JS symbols as DLL exported/visible')
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -1229,30 +1229,30 @@ bool ModuleBuilder::buildTables(frontend
   for (const frontend::StencilModuleEntry& exp : exportEntries_) {
     if (!exp.specifier) {
       frontend::StencilModuleEntry* importEntry = importEntryFor(exp.localName);
       if (!importEntry) {
         if (!metadata.localExportEntries.append(exp)) {
           return false;
         }
       } else {
-        if (importEntry->importName == cx_->names().star) {
+        if (importEntry->importName == cx_->parserNames().star) {
           if (!metadata.localExportEntries.append(exp)) {
             return false;
           }
         } else {
           auto entry = frontend::StencilModuleEntry::exportFromEntry(
               importEntry->specifier, importEntry->importName, exp.exportName,
               exp.lineno, exp.column);
           if (!metadata.indirectExportEntries.append(entry)) {
             return false;
           }
         }
       }
-    } else if (exp.importName == cx_->names().star && !exp.exportName) {
+    } else if (exp.importName == cx_->parserNames().star && !exp.exportName) {
       if (!metadata.starExportEntries.append(exp)) {
         return false;
       }
     } else {
       if (!metadata.indirectExportEntries.append(exp)) {
         return false;
       }
     }
@@ -1268,17 +1268,18 @@ void ModuleBuilder::finishFunctionDecls(
 
 enum class ModuleArrayType {
   ImportEntryObject,
   ExportEntryObject,
   RequestedModuleObject,
 };
 
 static ArrayObject* ModuleBuilderInitArray(
-    JSContext* cx, ModuleArrayType arrayType,
+    JSContext* cx, frontend::CompilationInfo& compilationInfo,
+    ModuleArrayType arrayType,
     const frontend::StencilModuleMetadata::EntryVector& vector) {
   RootedArrayObject resultArray(
       cx, NewDenseFullyAllocatedArray(cx, vector.length()));
   if (!resultArray) {
     return nullptr;
   }
 
   resultArray->ensureDenseInitializedLength(cx, 0, vector.length());
@@ -1286,20 +1287,44 @@ static ArrayObject* ModuleBuilderInitArr
   RootedAtom specifier(cx);
   RootedAtom localName(cx);
   RootedAtom importName(cx);
   RootedAtom exportName(cx);
   RootedObject req(cx);
 
   for (uint32_t i = 0; i < vector.length(); ++i) {
     const frontend::StencilModuleEntry& entry = vector[i];
-    specifier = entry.specifier;
-    localName = entry.localName;
-    importName = entry.importName;
-    exportName = entry.exportName;
+
+    if (entry.specifier) {
+      specifier = compilationInfo.liftParserAtomToJSAtom(entry.specifier);
+      if (!specifier) {
+        return nullptr;
+      }
+    }
+
+    if (entry.localName) {
+      localName = compilationInfo.liftParserAtomToJSAtom(entry.localName);
+      if (!localName) {
+        return nullptr;
+      }
+    }
+
+    if (entry.importName) {
+      importName = compilationInfo.liftParserAtomToJSAtom(entry.importName);
+      if (!importName) {
+        return nullptr;
+      }
+    }
+
+    if (entry.exportName) {
+      exportName = compilationInfo.liftParserAtomToJSAtom(entry.exportName);
+      if (!exportName) {
+        return nullptr;
+      }
+    }
 
     switch (arrayType) {
       case ModuleArrayType::ImportEntryObject:
         MOZ_ASSERT(localName && importName);
         req = ImportEntryObject::create(cx, specifier, importName, localName,
                                         entry.lineno, entry.column);
         break;
       case ModuleArrayType::ExportEntryObject:
@@ -1322,47 +1347,53 @@ static ArrayObject* ModuleBuilderInitArr
     resultArray->initDenseElement(i, ObjectValue(*req));
   }
 
   return resultArray;
 }
 
 // Use StencilModuleMetadata data to fill in ModuleObject
 bool frontend::StencilModuleMetadata::initModule(
-    JSContext* cx, JS::Handle<ModuleObject*> module) {
+    JSContext* cx, frontend::CompilationInfo& compilationInfo,
+    JS::Handle<ModuleObject*> module) {
   RootedArrayObject requestedModulesObject(
-      cx, ModuleBuilderInitArray(cx, ModuleArrayType::RequestedModuleObject,
+      cx, ModuleBuilderInitArray(cx, compilationInfo,
+                                 ModuleArrayType::RequestedModuleObject,
                                  requestedModules));
   if (!requestedModulesObject) {
     return false;
   }
 
   RootedArrayObject importEntriesObject(
-      cx, ModuleBuilderInitArray(cx, ModuleArrayType::ImportEntryObject,
+      cx, ModuleBuilderInitArray(cx, compilationInfo,
+                                 ModuleArrayType::ImportEntryObject,
                                  importEntries));
   if (!importEntriesObject) {
     return false;
   }
 
   RootedArrayObject localExportEntriesObject(
-      cx, ModuleBuilderInitArray(cx, ModuleArrayType::ExportEntryObject,
+      cx, ModuleBuilderInitArray(cx, compilationInfo,
+                                 ModuleArrayType::ExportEntryObject,
                                  localExportEntries));
   if (!localExportEntriesObject) {
     return false;
   }
 
   RootedArrayObject indirectExportEntriesObject(
-      cx, ModuleBuilderInitArray(cx, ModuleArrayType::ExportEntryObject,
+      cx, ModuleBuilderInitArray(cx, compilationInfo,
+                                 ModuleArrayType::ExportEntryObject,
                                  indirectExportEntries));
   if (!indirectExportEntriesObject) {
     return false;
   }
 
   RootedArrayObject starExportEntriesObject(
-      cx, ModuleBuilderInitArray(cx, ModuleArrayType::ExportEntryObject,
+      cx, ModuleBuilderInitArray(cx, compilationInfo,
+                                 ModuleArrayType::ExportEntryObject,
                                  starExportEntries));
   if (!starExportEntriesObject) {
     return false;
   }
 
   // Transfer the vector of declarations to the ModuleObject.
   module->initFunctionDeclarations(std::move(functionDecls));
 
@@ -1379,33 +1410,30 @@ bool ModuleBuilder::processImport(fronte
   MOZ_ASSERT(importNode->isKind(ParseNodeKind::ImportDecl));
 
   ListNode* specList = &importNode->left()->as<ListNode>();
   MOZ_ASSERT(specList->isKind(ParseNodeKind::ImportSpecList));
 
   NameNode* moduleSpec = &importNode->right()->as<NameNode>();
   MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::StringExpr));
 
-  RootedAtom module(cx_, moduleSpec->atom());
+  const ParserAtom* module = moduleSpec->atom();
   if (!maybeAppendRequestedModule(module, moduleSpec)) {
     return false;
   }
 
-  RootedAtom importName(cx_);
-  RootedAtom localName(cx_);
   for (ParseNode* item : specList->contents()) {
     BinaryNode* spec = &item->as<BinaryNode>();
     MOZ_ASSERT(spec->isKind(ParseNodeKind::ImportSpec));
 
     NameNode* importNameNode = &spec->left()->as<NameNode>();
-
     NameNode* localNameNode = &spec->right()->as<NameNode>();
 
-    importName = importNameNode->atom();
-    localName = localNameNode->atom();
+    const ParserAtom* importName = importNameNode->atom();
+    const ParserAtom* localName = localNameNode->atom();
 
     uint32_t line;
     uint32_t column;
     eitherParser_.computeLineAndColumn(importNameNode->pn_pos.begin, &line,
                                        &column);
 
     auto entry = frontend::StencilModuleEntry::importEntry(
         module, localName, importName, line, column);
@@ -1424,68 +1452,69 @@ bool ModuleBuilder::processExport(fronte
              exportNode->isKind(ParseNodeKind::ExportDefaultStmt));
 
   bool isDefault = exportNode->isKind(ParseNodeKind::ExportDefaultStmt);
   ParseNode* kid = isDefault ? exportNode->as<BinaryNode>().left()
                              : exportNode->as<UnaryNode>().kid();
 
   if (isDefault && exportNode->as<BinaryNode>().right()) {
     // This is an export default containing an expression.
-    HandlePropertyName localName = cx_->names().default_;
-    HandlePropertyName exportName = cx_->names().default_;
+    const ParserAtom* localName = cx_->parserNames().default_;
+    const ParserAtom* exportName = cx_->parserNames().default_;
     return appendExportEntry(exportName, localName);
   }
 
   switch (kid->getKind()) {
     case ParseNodeKind::ExportSpecList: {
       MOZ_ASSERT(!isDefault);
-      RootedAtom localName(cx_);
-      RootedAtom exportName(cx_);
       for (ParseNode* item : kid->as<ListNode>().contents()) {
         BinaryNode* spec = &item->as<BinaryNode>();
         MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportSpec));
 
         NameNode* localNameNode = &spec->left()->as<NameNode>();
         NameNode* exportNameNode = &spec->right()->as<NameNode>();
-        localName = localNameNode->atom();
-        exportName = exportNameNode->atom();
+
+        const ParserAtom* localName = localNameNode->atom();
+        const ParserAtom* exportName = exportNameNode->atom();
+
         if (!appendExportEntry(exportName, localName, spec)) {
           return false;
         }
       }
       break;
     }
 
     case ParseNodeKind::ClassDecl: {
       const ClassNode& cls = kid->as<ClassNode>();
       MOZ_ASSERT(cls.names());
-      RootedAtom localName(cx_, cls.names()->innerBinding()->atom());
-      RootedAtom exportName(
-          cx_, isDefault ? cx_->names().default_ : localName.get());
+      const frontend::ParserAtom* localName =
+          cls.names()->innerBinding()->atom();
+      const frontend::ParserAtom* exportName =
+          isDefault ? cx_->parserNames().default_ : localName;
       if (!appendExportEntry(exportName, localName)) {
         return false;
       }
       break;
     }
 
     case ParseNodeKind::VarStmt:
     case ParseNodeKind::ConstDecl:
     case ParseNodeKind::LetDecl: {
-      RootedAtom localName(cx_);
-      RootedAtom exportName(cx_);
       for (ParseNode* binding : kid->as<ListNode>().contents()) {
         if (binding->isKind(ParseNodeKind::AssignExpr)) {
           binding = binding->as<AssignmentNode>().left();
         } else {
           MOZ_ASSERT(binding->isKind(ParseNodeKind::Name));
         }
 
         if (binding->isKind(ParseNodeKind::Name)) {
-          localName = binding->as<NameNode>().atom();
-          exportName = isDefault ? cx_->names().default_ : localName.get();
+          const frontend::ParserAtom* localName =
+              binding->as<NameNode>().atom();
+          const frontend::ParserAtom* exportName =
+              isDefault ? cx_->parserNames().default_ : localName;
           if (!appendExportEntry(exportName, localName)) {
             return false;
           }
         } else if (binding->isKind(ParseNodeKind::ArrayExpr)) {
           if (!processExportArrayBinding(&binding->as<ListNode>())) {
             return false;
           }
         } else {
@@ -1496,20 +1525,19 @@ bool ModuleBuilder::processExport(fronte
         }
       }
       break;
     }
 
     case ParseNodeKind::Function: {
       FunctionBox* box = kid->as<FunctionNode>().funbox();
       MOZ_ASSERT(!box->isArrow());
-      RootedAtom localName(cx_, box->explicitName());
-      RootedAtom exportName(
-          cx_, isDefault ? cx_->names().default_ : localName.get());
-      MOZ_ASSERT_IF(isDefault, localName);
+      const frontend::ParserAtom* localName = box->explicitName();
+      const frontend::ParserAtom* exportName =
+          isDefault ? cx_->parserNames().default_ : localName;
       if (!appendExportEntry(exportName, localName)) {
         return false;
       }
       break;
     }
 
     default:
       MOZ_CRASH("Unexpected parse node");
@@ -1517,17 +1545,17 @@ bool ModuleBuilder::processExport(fronte
 
   return true;
 }
 
 bool ModuleBuilder::processExportBinding(frontend::ParseNode* binding) {
   using namespace js::frontend;
 
   if (binding->isKind(ParseNodeKind::Name)) {
-    RootedAtom name(cx_, binding->as<NameNode>().atom());
+    const frontend::ParserAtom* name = binding->as<NameNode>().atom();
     return appendExportEntry(name, name);
   }
 
   if (binding->isKind(ParseNodeKind::ArrayExpr)) {
     return processExportArrayBinding(&binding->as<ListNode>());
   }
 
   MOZ_ASSERT(binding->isKind(ParseNodeKind::ObjectExpr));
@@ -1598,64 +1626,65 @@ bool ModuleBuilder::processExportFrom(fr
   MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportFromStmt));
 
   ListNode* specList = &exportNode->left()->as<ListNode>();
   MOZ_ASSERT(specList->isKind(ParseNodeKind::ExportSpecList));
 
   NameNode* moduleSpec = &exportNode->right()->as<NameNode>();
   MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::StringExpr));
 
-  RootedAtom module(cx_, moduleSpec->atom());
+  const frontend::ParserAtom* module = moduleSpec->atom();
+
   if (!maybeAppendRequestedModule(module, moduleSpec)) {
     return false;
   }
 
-  RootedAtom bindingName(cx_);
-  RootedAtom exportName(cx_);
   for (ParseNode* spec : specList->contents()) {
     if (spec->isKind(ParseNodeKind::ExportSpec)) {
       NameNode* localNameNode = &spec->as<BinaryNode>().left()->as<NameNode>();
       NameNode* exportNameNode =
           &spec->as<BinaryNode>().right()->as<NameNode>();
-      bindingName = localNameNode->atom();
-      exportName = exportNameNode->atom();
+
+      const frontend::ParserAtom* bindingName = localNameNode->atom();
+      const frontend::ParserAtom* exportName = exportNameNode->atom();
+
       if (!appendExportFromEntry(exportName, module, bindingName,
                                  localNameNode)) {
         return false;
       }
     } else {
       MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportBatchSpecStmt));
-      exportName = cx_->names().star;
+      const frontend::ParserAtom* exportName = cx_->parserNames().star;
       if (!appendExportFromEntry(nullptr, module, exportName, spec)) {
         return false;
       }
     }
   }
 
   return true;
 }
 
 frontend::StencilModuleEntry* ModuleBuilder::importEntryFor(
-    JSAtom* localName) const {
+    const frontend::ParserAtom* localName) const {
   MOZ_ASSERT(localName);
   auto ptr = importEntries_.lookup(localName);
   if (!ptr) {
     return nullptr;
   }
 
   return &ptr->value();
 }
 
-bool ModuleBuilder::hasExportedName(JSAtom* name) const {
+bool ModuleBuilder::hasExportedName(const frontend::ParserAtom* name) const {
   MOZ_ASSERT(name);
   return exportNames_.has(name);
 }
 
-bool ModuleBuilder::appendExportEntry(HandleAtom exportName,
-                                      HandleAtom localName,
+bool ModuleBuilder::appendExportEntry(const frontend::ParserAtom* exportName,
+                                      const frontend::ParserAtom* localName,
                                       frontend::ParseNode* node) {
   uint32_t line = 0;
   uint32_t column = 0;
   if (node) {
     eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
   }
 
   auto entry = frontend::StencilModuleEntry::exportAsEntry(
@@ -1668,35 +1697,35 @@ bool ModuleBuilder::appendExportEntry(Ha
     if (!exportNames_.put(exportName)) {
       return false;
     }
   }
 
   return true;
 }
 
-bool ModuleBuilder::appendExportFromEntry(HandleAtom exportName,
-                                          HandleAtom moduleRequest,
-                                          HandleAtom importName,
-                                          frontend::ParseNode* node) {
+bool ModuleBuilder::appendExportFromEntry(
+    const frontend::ParserAtom* exportName,
+    const frontend::ParserAtom* moduleRequest,
+    const frontend::ParserAtom* importName, frontend::ParseNode* node) {
   uint32_t line;
   uint32_t column;
   eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
 
   auto entry = frontend::StencilModuleEntry::exportFromEntry(
       moduleRequest, importName, exportName, line, column);
   if (!exportEntries_.append(entry)) {
     return false;
   }
 
   return !exportName || exportNames_.put(exportName);
 }
 
-bool ModuleBuilder::maybeAppendRequestedModule(HandleAtom specifier,
-                                               frontend::ParseNode* node) {
+bool ModuleBuilder::maybeAppendRequestedModule(
+    const frontend::ParserAtom* specifier, frontend::ParseNode* node) {
   if (requestedModuleSpecifiers_.has(specifier)) {
     return true;
   }
 
   uint32_t line;
   uint32_t column;
   eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
 
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -2468,30 +2468,39 @@ bool ASTSerializer::statement(ParseNode*
              builder.forStatement(init, test, update, stmt, &forNode->pn_pos,
                                   dst);
     }
 
     case ParseNodeKind::BreakStmt:
     case ParseNodeKind::ContinueStmt: {
       LoopControlStatement* node = &pn->as<LoopControlStatement>();
       RootedValue label(cx);
-      RootedAtom pnAtom(cx, node->label());
+      RootedAtom pnAtom(cx);
+      if (node->label()) {
+        pnAtom.set(parser->liftParserAtomToJSAtom(node->label()));
+        if (!pnAtom) {
+          return false;
+        }
+      }
       return optIdentifier(pnAtom, nullptr, &label) &&
              (node->isKind(ParseNodeKind::BreakStmt)
                   ? builder.breakStatement(label, &node->pn_pos, dst)
                   : builder.continueStatement(label, &node->pn_pos, dst));
     }
 
     case ParseNodeKind::LabelStmt: {
       LabeledStatement* labelNode = &pn->as<LabeledStatement>();
       ParseNode* stmtNode = labelNode->statement();
       MOZ_ASSERT(labelNode->pn_pos.encloses(stmtNode->pn_pos));
 
       RootedValue label(cx), stmt(cx);
-      RootedAtom pnAtom(cx, labelNode->label());
+      RootedAtom pnAtom(cx, parser->liftParserAtomToJSAtom(labelNode->label()));
+      if (!pnAtom.get()) {
+        return false;
+      }
       return identifier(pnAtom, nullptr, &label) &&
              statement(stmtNode, &stmt) &&
              builder.labeledStatement(label, stmt, &labelNode->pn_pos, dst);
     }
 
     case ParseNodeKind::ThrowStmt: {
       UnaryNode* throwNode = &pn->as<UnaryNode>();
       ParseNode* operand = throwNode->kid();
@@ -2914,17 +2923,20 @@ bool ASTSerializer::expression(ParseNode
       PropertyAccessBase* prop = &pn->as<PropertyAccessBase>();
       MOZ_ASSERT(prop->pn_pos.encloses(prop->expression().pn_pos));
 
       bool isSuper =
           prop->is<PropertyAccess>() && prop->as<PropertyAccess>().isSuper();
 
       RootedValue expr(cx);
       RootedValue propname(cx);
-      RootedAtom pnAtom(cx, prop->key().atom());
+      RootedAtom pnAtom(cx, parser->liftParserAtomToJSAtom(prop->key().atom()));
+      if (!pnAtom.get()) {
+        return false;
+      }
 
       if (isSuper) {
         if (!builder.super(&prop->expression().pn_pos, &expr)) {
           return false;
         }
       } else {
         if (!expression(&prop->expression(), &expr)) {
           return false;
@@ -2972,18 +2984,21 @@ bool ASTSerializer::expression(ParseNode
       NodeVector raw(cx);
       if (!raw.reserve(rawNodes->count())) {
         return false;
       }
       for (ParseNode* item : rawNodes->contents()) {
         NameNode* rawItem = &item->as<NameNode>();
         MOZ_ASSERT(callSiteObj->pn_pos.encloses(rawItem->pn_pos));
 
-        RootedValue expr(cx);
-        expr.setString(rawItem->atom());
+        JSAtom* exprAtom = parser->liftParserAtomToJSAtom(rawItem->atom());
+        if (!exprAtom) {
+          return false;
+        }
+        RootedValue expr(cx, StringValue(exprAtom));
         raw.infallibleAppend(expr);
       }
 
       NodeVector cooked(cx);
       if (!cooked.reserve(callSiteObj->count() - 1)) {
         return false;
       }
 
@@ -2991,17 +3006,22 @@ bool ASTSerializer::expression(ParseNode
            callSiteObj->contentsFrom(rawNodes->pn_next)) {
         MOZ_ASSERT(callSiteObj->pn_pos.encloses(cookedItem->pn_pos));
 
         RootedValue expr(cx);
         if (cookedItem->isKind(ParseNodeKind::RawUndefinedExpr)) {
           expr.setUndefined();
         } else {
           MOZ_ASSERT(cookedItem->isKind(ParseNodeKind::TemplateStringExpr));
-          expr.setString(cookedItem->as<NameNode>().atom());
+          JSAtom* exprAtom =
+              parser->liftParserAtomToJSAtom(cookedItem->as<NameNode>().atom());
+          if (!exprAtom) {
+            return false;
+          }
+          expr.setString(exprAtom);
         }
         cooked.infallibleAppend(expr);
       }
 
       return builder.callSiteObj(raw, cooked, &callSiteObj->pn_pos, dst);
     }
 
     case ParseNodeKind::ArrayExpr: {
@@ -3245,19 +3265,25 @@ bool ASTSerializer::property(ParseNode* 
          builder.propertyInitializer(key, val, kind, isShorthand, isMethod,
                                      &node->pn_pos, dst);
 }
 
 bool ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) {
   RootedValue val(cx);
   switch (pn->getKind()) {
     case ParseNodeKind::TemplateStringExpr:
-    case ParseNodeKind::StringExpr:
-      val.setString(pn->as<NameNode>().atom());
+    case ParseNodeKind::StringExpr: {
+      JSAtom* exprAtom =
+          parser->liftParserAtomToJSAtom(pn->as<NameNode>().atom());
+      if (!exprAtom) {
+        return false;
+      }
+      val.setString(exprAtom);
       break;
+    }
 
     case ParseNodeKind::RegExpExpr: {
       RegExpObject* re =
           pn->as<RegExpLiteral>().create(cx, parser->getCompilationInfo());
       if (!re) {
         return false;
       }
 
@@ -3410,32 +3436,41 @@ bool ASTSerializer::identifier(HandleAto
                                MutableHandleValue dst) {
   RootedValue atomContentsVal(cx, unrootedAtomContents(atom));
   return builder.identifier(atomContentsVal, pos, dst);
 }
 
 bool ASTSerializer::identifier(NameNode* id, MutableHandleValue dst) {
   LOCAL_ASSERT(id->atom());
 
-  RootedAtom pnAtom(cx, id->atom());
+  RootedAtom pnAtom(cx, parser->liftParserAtomToJSAtom(id->atom()));
+  if (!pnAtom.get()) {
+    return false;
+  }
   return identifier(pnAtom, &id->pn_pos, dst);
 }
 
 bool ASTSerializer::function(FunctionNode* funNode, ASTType type,
                              MutableHandleValue dst) {
   FunctionBox* funbox = funNode->funbox();
 
   GeneratorStyle generatorStyle =
       funbox->isGenerator() ? GeneratorStyle::ES6 : GeneratorStyle::None;
 
   bool isAsync = funbox->isAsync();
   bool isExpression = funbox->hasExprBody();
 
   RootedValue id(cx);
-  RootedAtom funcAtom(cx, funbox->explicitName());
+  RootedAtom funcAtom(cx);
+  if (funbox->explicitName()) {
+    funcAtom.set(parser->liftParserAtomToJSAtom(funbox->explicitName()));
+    if (!funcAtom) {
+      return false;
+    }
+  }
   if (!optIdentifier(funcAtom, nullptr, &id)) {
     return false;
   }
 
   NodeVector args(cx);
   NodeVector defaults(cx);
 
   RootedValue body(cx), rest(cx);
--- a/js/src/frontend/BytecodeCompiler.h
+++ b/js/src/frontend/BytecodeCompiler.h
@@ -102,16 +102,17 @@ namespace js {
 class ModuleObject;
 class ScriptSourceObject;
 
 namespace frontend {
 
 class ErrorReporter;
 class FunctionBox;
 class ParseNode;
+class ParserAtom;
 
 // Compile a module of the given source using the given options.
 ModuleObject* CompileModule(JSContext* cx,
                             const JS::ReadOnlyCompileOptions& options,
                             JS::SourceText<char16_t>& srcBuf);
 ModuleObject* CompileModule(JSContext* cx,
                             const JS::ReadOnlyCompileOptions& options,
                             JS::SourceText<mozilla::Utf8Unit>& srcBuf);
@@ -169,29 +170,32 @@ MOZ_MUST_USE JSFunction* CompileStandalo
  * more IdentifierPart characters, i.e. it matches the IdentifierName production
  * in the language spec.
  *
  * This returns true even if str is a keyword like "if".
  *
  * Defined in TokenStream.cpp.
  */
 bool IsIdentifier(JSLinearString* str);
+bool IsIdentifier(const ParserAtom* atom);
 
 bool IsIdentifierNameOrPrivateName(JSLinearString* str);
+bool IsIdentifierNameOrPrivateName(const ParserAtom* atom);
 
 /*
  * As above, but taking chars + length.
  */
 bool IsIdentifier(const Latin1Char* chars, size_t length);
 bool IsIdentifier(const char16_t* chars, size_t length);
 
 bool IsIdentifierNameOrPrivateName(const Latin1Char* chars, size_t length);
 bool IsIdentifierNameOrPrivateName(const char16_t* chars, size_t length);
 
 /* True if str is a keyword. Defined in TokenStream.cpp. */
+bool IsKeyword(const ParserAtom* atom);
 bool IsKeyword(JSLinearString* str);
 
 class MOZ_STACK_CLASS AutoFrontendTraceLog {
 #ifdef JS_TRACE_LOGGING
   TraceLoggerThread* logger_;
   mozilla::Maybe<TraceLoggerEvent> frontendEvent_;
   mozilla::Maybe<AutoTraceLog> frontendLog_;
   mozilla::Maybe<AutoTraceLog> typeLog_;
--- a/js/src/frontend/BytecodeControlStructures.cpp
+++ b/js/src/frontend/BytecodeControlStructures.cpp
@@ -24,20 +24,20 @@ BreakableControl::BreakableControl(Bytec
     : NestableControl(bce, kind) {
   MOZ_ASSERT(is<BreakableControl>());
 }
 
 bool BreakableControl::patchBreaks(BytecodeEmitter* bce) {
   return bce->emitJumpTargetAndPatch(breaks);
 }
 
-LabelControl::LabelControl(BytecodeEmitter* bce, JSAtom* label,
+LabelControl::LabelControl(BytecodeEmitter* bce, const ParserAtom* label,
                            BytecodeOffset startOffset)
     : BreakableControl(bce, StatementKind::Label),
-      label_(bce->cx, label),
+      label_(label),
       startOffset_(startOffset) {}
 
 LoopControl::LoopControl(BytecodeEmitter* bce, StatementKind loopKind)
     : BreakableControl(bce, loopKind), tdzCache_(bce) {
   MOZ_ASSERT(is<LoopControl>());
 
   LoopControl* enclosingLoop = findNearest<LoopControl>(enclosing());
 
--- a/js/src/frontend/BytecodeControlStructures.h
+++ b/js/src/frontend/BytecodeControlStructures.h
@@ -11,21 +11,20 @@
 #include "mozilla/Attributes.h"  // MOZ_MUST_USE
 #include "mozilla/Maybe.h"       // mozilla::Maybe
 
 #include <stdint.h>  // int32_t, uint32_t
 
 #include "ds/Nestable.h"               // Nestable
 #include "frontend/BytecodeSection.h"  // BytecodeOffset
 #include "frontend/JumpList.h"         // JumpList, JumpTarget
+#include "frontend/ParserAtom.h"       // ParserAtom
 #include "frontend/SharedContext.h"  // StatementKind, StatementKindIsLoop, StatementKindIsUnlabeledBreakTarget
 #include "frontend/TDZCheckCache.h"  // TDZCheckCache
-#include "gc/Rooting.h"              // RootedAtom, HandleAtom
 #include "vm/StencilEnums.h"         // TryNoteKind
-#include "vm/StringType.h"           // JSAtom
 
 namespace js {
 namespace frontend {
 
 struct BytecodeEmitter;
 class EmitterScope;
 
 class NestableControl : public Nestable<NestableControl> {
@@ -66,25 +65,26 @@ class BreakableControl : public Nestable
 };
 template <>
 inline bool NestableControl::is<BreakableControl>() const {
   return StatementKindIsUnlabeledBreakTarget(kind_) ||
          kind_ == StatementKind::Label;
 }
 
 class LabelControl : public BreakableControl {
-  RootedAtom label_;
+  const ParserAtom* label_;
 
   // The code offset when this was pushed. Used for effectfulness checking.
   BytecodeOffset startOffset_;
 
  public:
-  LabelControl(BytecodeEmitter* bce, JSAtom* label, BytecodeOffset startOffset);
+  LabelControl(BytecodeEmitter* bce, const ParserAtom* label,
+               BytecodeOffset startOffset);
 
-  HandleAtom label() const { return label_; }
+  const ParserAtom* label() const { return label_; }
 
   BytecodeOffset startOffset() const { return startOffset_; }
 };
 template <>
 inline bool NestableControl::is<LabelControl>() const {
   return kind_ == StatementKind::Label;
 }
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -45,16 +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/PropOpEmitter.h"  // PropOpEmitter
 #include "frontend/SourceNotes.h"    // SrcNote, SrcNoteType, SrcNoteWriter
 #include "frontend/SwitchEmitter.h"  // SwitchEmitter
 #include "frontend/TDZCheckCache.h"  // TDZCheckCache
 #include "frontend/TryEmitter.h"     // TryEmitter
 #include "frontend/WhileEmitter.h"   // WhileEmitter
 #include "js/CompileOptions.h"       // TransitiveCompileOptions, CompileOptions
 #include "js/friend/StackLimits.h"   // CheckRecursionLimit
@@ -167,27 +168,27 @@ T* BytecodeEmitter::findInnermostNestabl
   return NestableControl::findNearest<T>(innermostNestableControl);
 }
 
 template <typename T, typename Predicate /* (T*) -> bool */>
 T* BytecodeEmitter::findInnermostNestableControl(Predicate predicate) const {
   return NestableControl::findNearest<T>(innermostNestableControl, predicate);
 }
 
-NameLocation BytecodeEmitter::lookupName(JSAtom* name) {
+NameLocation BytecodeEmitter::lookupName(const ParserAtom* name) {
   return innermostEmitterScope()->lookup(this, name);
 }
 
 Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInScope(
-    JSAtom* name, EmitterScope* target) {
+    const ParserAtom* name, EmitterScope* target) {
   return innermostEmitterScope()->locationBoundInScope(name, target);
 }
 
 Maybe<NameLocation> BytecodeEmitter::locationOfNameBoundInFunctionScope(
-    JSAtom* name, EmitterScope* source) {
+    const ParserAtom* name, EmitterScope* source) {
   EmitterScope* funScope = source;
   while (!funScope->scope(this).is<FunctionScope>()) {
     funScope = funScope->enclosingInFrame();
   }
   return source->locationBoundInScope(name, funScope);
 }
 
 bool BytecodeEmitter::markStepBreakpoint() {
@@ -898,17 +899,17 @@ bool BytecodeEmitter::emitGCIndexOp(JSOp
 
   jsbytecode* code = bytecodeSection().code(offset);
   code[0] = jsbytecode(op);
   SET_GCTHING_INDEX(code, index);
   bytecodeSection().updateDepth(offset);
   return true;
 }
 
-bool BytecodeEmitter::emitAtomOp(JSOp op, JSAtom* atom,
+bool BytecodeEmitter::emitAtomOp(JSOp op, const ParserAtom* atom,
                                  ShouldInstrument shouldInstrument) {
   MOZ_ASSERT(atom);
 
   // .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,
@@ -1651,18 +1652,19 @@ bool BytecodeEmitter::iteratorResultShap
   ObjLiteralIndex objIndex(compilationInfo.objLiteralData.length());
   if (!compilationInfo.objLiteralData.emplaceBack(cx)) {
     return false;
   }
   ObjLiteralStencil& data = compilationInfo.objLiteralData.back();
 
   data.writer().beginObject(flags);
 
-  for (auto name : {&JSAtomState::value, &JSAtomState::done}) {
-    HandlePropertyName propName = cx->parserNames().*name;
+  using WellKnownName = js::frontend::WellKnownParserAtoms;
+  for (auto name : {&WellKnownName::value, &WellKnownName::done}) {
+    const ParserAtom* propName = cx->parserNames().*name;
 
     uint32_t propNameIndex = 0;
     if (!data.addAtom(propName, &propNameIndex)) {
       return false;
     }
     data.writer().setPropName(propNameIndex);
 
     if (!data.writer().propWithUndefinedValue()) {
@@ -1689,51 +1691,49 @@ bool BytecodeEmitter::emitFinishIterator
     return false;
   }
   if (!emitAtomOp(JSOp::InitProp, cx->parserNames().done)) {
     return false;
   }
   return true;
 }
 
-bool BytecodeEmitter::emitGetNameAtLocation(Handle<JSAtom*> name,
+bool BytecodeEmitter::emitGetNameAtLocation(const ParserAtom* name,
                                             const NameLocation& loc) {
   NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
   if (!noe.emitGet()) {
     return false;
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitGetName(NameNode* name) {
   MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
 
-  RootedAtom nameAtom(cx, name->name());
+  const ParserAtom* nameAtom = name->name();
   return emitGetName(nameAtom);
 }
 
 bool BytecodeEmitter::emitGetPrivateName(NameNode* name) {
   MOZ_ASSERT(name->isKind(ParseNodeKind::PrivateName));
   return emitGetPrivateName(name->name());
 }
 
-bool BytecodeEmitter::emitGetPrivateName(JSAtom* name) {
-  RootedAtom nameAtom(cx, name);
-
+bool BytecodeEmitter::emitGetPrivateName(const ParserAtom* nameAtom) {
   // The parser ensures the private name is present on the environment chain.
   NameLocation location = lookupName(nameAtom);
   MOZ_ASSERT(location.kind() == NameLocation::Kind::FrameSlot ||
              location.kind() == NameLocation::Kind::EnvironmentCoordinate ||
              location.kind() == NameLocation::Kind::Dynamic);
 
   return emitGetNameAtLocation(nameAtom, location);
 }
 
-bool BytecodeEmitter::emitTDZCheckIfNeeded(HandleAtom name,
+bool BytecodeEmitter::emitTDZCheckIfNeeded(const ParserAtom* name,
                                            const NameLocation& loc,
                                            ValueIsOnStack isOnStack) {
   // Dynamic accesses have TDZ checks built into their VM code and should
   // never emit explicit TDZ checks.
   MOZ_ASSERT(loc.hasKnownSlot());
   MOZ_ASSERT(loc.isLexical());
 
   Maybe<MaybeCheckTDZ> check =
@@ -1871,17 +1871,17 @@ bool BytecodeEmitter::emitPropIncDec(Una
   return true;
 }
 
 bool BytecodeEmitter::emitNameIncDec(UnaryNode* incDec) {
   MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name));
 
   ParseNodeKind kind = incDec->getKind();
   NameNode* name = &incDec->kid()->as<NameNode>();
-  RootedAtom nameAtom(cx, name->atom());
+  const ParserAtom* nameAtom = name->atom();
   NameOpEmitter noe(this, nameAtom,
                     kind == ParseNodeKind::PostIncrementExpr
                         ? NameOpEmitter::Kind::PostIncrement
                         : kind == ParseNodeKind::PreIncrementExpr
                               ? NameOpEmitter::Kind::PreIncrement
                               : kind == ParseNodeKind::PostDecrementExpr
                                     ? NameOpEmitter::Kind::PostDecrement
                                     : NameOpEmitter::Kind::PreDecrement);
@@ -2301,17 +2301,17 @@ bool BytecodeEmitter::emitPushResumeKind
 
 bool BytecodeEmitter::emitSetThis(BinaryNode* setThisNode) {
   // ParseNodeKind::SetThis is used to update |this| after a super() call
   // in a derived class constructor.
 
   MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis));
   MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name));
 
-  RootedAtom name(cx, setThisNode->left()->as<NameNode>().name());
+  const ParserAtom* name = setThisNode->left()->as<NameNode>().name();
 
   // The 'this' binding is not lexical, but due to super() semantics this
   // initialization needs to be treated as a lexical one.
   NameLocation loc = lookupName(name);
   NameLocation lexicalLoc;
   if (loc.kind() == NameLocation::Kind::FrameSlot) {
     lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot());
   } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) {
@@ -2468,22 +2468,22 @@ bool BytecodeEmitter::emitScript(ParseNo
   if (!emitReturnRval()) {
     return false;
   }
 
   if (!emitterScope.leave(this)) {
     return false;
   }
 
-  if (!NameFunctions(cx, body)) {
+  if (!NameFunctions(cx, compilationInfo, body)) {
     return false;
   }
 
   // Create a Stencil and convert it into a JSScript.
-  return intoScriptStencil(compilationInfo.topLevel.address());
+  return intoScriptStencil(&compilationInfo.topLevel.get());
 }
 
 js::UniquePtr<ImmutableScriptData> BytecodeEmitter::createImmutableScriptData(
     JSContext* cx) {
   uint32_t nslots;
   if (!getNslots(&nslots)) {
     return nullptr;
   }
@@ -2547,17 +2547,17 @@ bool BytecodeEmitter::emitFunctionScript
   }
 
   if (!fse.emitEndBody()) {
     //              [stack]
     return false;
   }
 
   if (isTopLevel == TopLevelFunction::Yes) {
-    if (!NameFunctions(cx, funNode)) {
+    if (!NameFunctions(cx, compilationInfo, funNode)) {
       return false;
     }
   }
 
   return fse.intoStencil();
 }
 
 bool BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target,
@@ -2686,17 +2686,17 @@ bool BytecodeEmitter::emitSetOrInitializ
     // Per its post-condition, emitDestructuringOps has left the
     // to-be-destructured value on top of the stack.
     if (!emit1(JSOp::Pop)) {
       return false;
     }
   } else {
     switch (target->getKind()) {
       case ParseNodeKind::Name: {
-        RootedAtom name(cx, target->as<NameNode>().name());
+        const ParserAtom* name = target->as<NameNode>().name();
         NameLocation loc = lookupName(name);
         NameOpEmitter::Kind kind;
         switch (flav) {
           case DestructuringFlavor::Declaration:
             kind = NameOpEmitter::Kind::Initialize;
             break;
 
           case DestructuringFlavor::Assignment:
@@ -3090,17 +3090,17 @@ bool BytecodeEmitter::emitDefault(ParseN
   if (!de.emitEnd()) {
     //              [stack] VALUE/DEFAULTVALUE
     return false;
   }
   return true;
 }
 
 bool BytecodeEmitter::emitAnonymousFunctionWithName(ParseNode* node,
-                                                    HandleAtom name) {
+                                                    const ParserAtom* name) {
   MOZ_ASSERT(node->isDirectRHSAnonFunction());
 
   if (node->is<FunctionNode>()) {
     // Function doesn't have 'name' property at this point.
     // Set function's name at compile time.
     if (!setFunName(node->as<FunctionNode>().funbox(), name)) {
       return false;
     }
@@ -3134,17 +3134,17 @@ bool BytecodeEmitter::emitAnonymousFunct
   }
 
   MOZ_ASSERT(node->is<ClassNode>());
   MOZ_ASSERT(prefixKind == FunctionPrefixKind::None);
 
   return emitClass(&node->as<ClassNode>(), ClassNameKind::ComputedName);
 }
 
-bool BytecodeEmitter::setFunName(FunctionBox* funbox, JSAtom* name) {
+bool BytecodeEmitter::setFunName(FunctionBox* funbox, const ParserAtom* name) {
   // The inferred name may already be set if this function is an interpreted
   // lazy function and we OOM'ed after we set the inferred name the first
   // time.
   if (funbox->hasInferredName()) {
     MOZ_ASSERT(!funbox->emitBytecode);
     MOZ_ASSERT(funbox->displayAtom() == name);
 
     return true;
@@ -3153,17 +3153,17 @@ bool BytecodeEmitter::setFunName(Functio
   funbox->setInferredName(name);
   return true;
 }
 
 bool BytecodeEmitter::emitInitializer(ParseNode* initializer,
                                       ParseNode* pattern) {
   if (initializer->isDirectRHSAnonFunction()) {
     MOZ_ASSERT(!pattern->isInParens());
-    RootedAtom name(cx, pattern->as<NameNode>().name());
+    const ParserAtom* name = pattern->as<NameNode>().name();
     if (!emitAnonymousFunctionWithName(initializer, name)) {
       return false;
     }
   } else {
     if (!emitTree(initializer)) {
       return false;
     }
   }
@@ -3831,41 +3831,41 @@ bool BytecodeEmitter::emitDestructuringO
   } else {
     // Take the slow but sure way and start off with a blank object.
     if (!emit1(JSOp::NewInit)) {
       //            [stack] OBJ
       return false;
     }
   }
 
-  RootedAtom pnatom(cx);
+  const ParserAtom* pnatom = nullptr;
   for (ParseNode* member : pattern->contents()) {
     if (member->isKind(ParseNodeKind::Spread)) {
       MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread");
       break;
     }
 
     bool isIndex = false;
     if (member->isKind(ParseNodeKind::MutateProto)) {
-      pnatom.set(cx->parserNames().proto);
+      pnatom = cx->parserNames().proto;
     } else {
       ParseNode* key = member->as<BinaryNode>().left();
       if (key->isKind(ParseNodeKind::NumberExpr)) {
         if (!emitNumberOp(key->as<NumericLiteral>().value())) {
           return false;
         }
         isIndex = true;
       } else if (key->isKind(ParseNodeKind::BigIntExpr)) {
         if (!emitBigIntOp(&key->as<BigIntLiteral>())) {
           return false;
         }
         isIndex = true;
       } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
                  key->isKind(ParseNodeKind::StringExpr)) {
-        pnatom.set(key->as<NameNode>().atom());
+        pnatom = key->as<NameNode>().atom();
       } else {
         // Otherwise this is a computed property name which needs to
         // be added dynamically.
         MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
         continue;
       }
     }
 
@@ -3901,17 +3901,17 @@ bool BytecodeEmitter::emitTemplateString
 
   for (ParseNode* item : templateString->contents()) {
     bool isString = (item->getKind() == ParseNodeKind::StringExpr ||
                      item->getKind() == ParseNodeKind::TemplateStringExpr);
 
     // Skip empty strings. These are very common: a template string like
     // `${a}${b}` has three empty strings and without this optimization
     // we'd emit four JSOp::Add operations instead of just one.
-    if (isString && item->as<NameNode>().atom()->empty()) {
+    if (isString && item->as<NameNode>().atom()->length() == 0) {
       continue;
     }
 
     if (!isString) {
       // We update source notes before emitting the expression
       if (!updateSourceCoordNotes(item->pn_pos.begin)) {
         return false;
       }
@@ -4001,17 +4001,17 @@ bool BytecodeEmitter::emitSingleDeclarat
                                             ParseNode* initializer) {
   MOZ_ASSERT(decl->isKind(ParseNodeKind::Name));
 
   // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
   if (!initializer && declList->isKind(ParseNodeKind::VarStmt)) {
     return true;
   }
 
-  RootedAtom nameAtom(cx, decl->name());
+  const ParserAtom* nameAtom = decl->name();
   NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize);
   if (!noe.prepareForRhs()) {
     //              [stack] ENV?
     return false;
   }
   if (!initializer) {
     // Lexical declarations are initialized to undefined without an
     // initializer.
@@ -4044,17 +4044,17 @@ bool BytecodeEmitter::emitSingleDeclarat
     //              [stack]
     return false;
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitAssignmentRhs(ParseNode* rhs,
-                                        HandleAtom anonFunctionName) {
+                                        const ParserAtom* anonFunctionName) {
   if (rhs->isDirectRHSAnonFunction()) {
     if (anonFunctionName) {
       return emitAnonymousFunctionWithName(rhs, anonFunctionName);
     }
     return emitAnonymousFunctionWithComputedName(rhs, FunctionPrefixKind::None);
   }
   return emitTree(rhs);
 }
@@ -4116,17 +4116,17 @@ bool BytecodeEmitter::emitAssignmentOrIn
   JSOp compoundOp = CompoundAssignmentParseNodeKindToJSOp(kind);
   bool isCompound = compoundOp != JSOp::Nop;
   bool isInit = kind == ParseNodeKind::InitExpr;
 
   MOZ_ASSERT_IF(isInit, lhs->isKind(ParseNodeKind::DotExpr) ||
                             lhs->isKind(ParseNodeKind::ElemExpr));
 
   // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|.
-  RootedAtom name(cx);
+  const ParserAtom* name = nullptr;
 
   Maybe<NameOpEmitter> noe;
   Maybe<PropOpEmitter> poe;
   Maybe<ElemOpEmitter> eoe;
 
   // Deal with non-name assignments.
   uint8_t offset = 1;
 
@@ -4137,17 +4137,17 @@ bool BytecodeEmitter::emitAssignmentOrIn
   //
   // In normal property assignments (`obj.x = function(){}`), the anonymous
   // function does not have a computed name, and rhs->isDirectRHSAnonFunction()
   // will be false (and anonFunctionName will not be used). However, in field
   // initializers (`class C { x = function(){} }`), field initialization is
   // implemented via a property or elem assignment (where we are now), and
   // rhs->isDirectRHSAnonFunction() is set - so we'll assign the name of the
   // function.
-  RootedAtom anonFunctionName(cx);
+  const ParserAtom* anonFunctionName = nullptr;
 
   switch (lhs->getKind()) {
     case ParseNodeKind::Name: {
       name = lhs->as<NameNode>().name();
       anonFunctionName = name;
       noe.emplace(this, name,
                   isCompound ? NameOpEmitter::Kind::CompoundAssignment
                              : NameOpEmitter::Kind::SimpleAssignment);
@@ -4160,17 +4160,17 @@ bool BytecodeEmitter::emitAssignmentOrIn
                   isCompound ? PropOpEmitter::Kind::CompoundAssignment
                              : isInit ? PropOpEmitter::Kind::PropInit
                                       : PropOpEmitter::Kind::SimpleAssignment,
                   isSuper ? PropOpEmitter::ObjKind::Super
                           : PropOpEmitter::ObjKind::Other);
       if (!poe->prepareForObj()) {
         return false;
       }
-      anonFunctionName = &prop->name();
+      anonFunctionName = prop->name();
       if (isSuper) {
         UnaryNode* base = &prop->expression().as<UnaryNode>();
         if (!emitGetThisForSuperBase(base)) {
           //        [stack] THIS SUPERBASE
           return false;
         }
         // SUPERBASE is pushed onto THIS later in poe->emitGet below.
         offset += 2;
@@ -4383,17 +4383,17 @@ bool BytecodeEmitter::emitShortCircuitAs
     default:
       MOZ_CRASH("Unexpected ParseNodeKind");
   }
 
   ParseNode* lhs = node->left();
   ParseNode* rhs = node->right();
 
   // |name| is used within NameOpEmitter, so its lifetime must surpass |noe|.
-  RootedAtom name(cx);
+  const ParserAtom* name = nullptr;
 
   // Select the appropriate emitter based on the left-hand side.
   Maybe<NameOpEmitter> noe;
   Maybe<PropOpEmitter> poe;
   Maybe<ElemOpEmitter> eoe;
 
   int32_t depth = bytecodeSection().stackDepth();
 
@@ -5333,17 +5333,17 @@ bool BytecodeEmitter::emitInitializeForI
              target->isKind(ParseNodeKind::InitExpr)) {
     BinaryNode* assignNode = &target->as<BinaryNode>();
     if (assignNode->left()->is<NameNode>()) {
       nameNode = &assignNode->left()->as<NameNode>();
     }
   }
 
   if (nameNode) {
-    RootedAtom nameAtom(cx, nameNode->name());
+    const ParserAtom* nameAtom = nameNode->name();
     NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize);
     if (!noe.prepareForRhs()) {
       return false;
     }
     if (noe.emittedBindOp()) {
       // Per-iteration initialization in for-in/of loops computes the
       // iteration value *before* initializing.  Thus the initializing
       // value may be buried under a bind-specific value on the stack.
@@ -5471,17 +5471,17 @@ bool BytecodeEmitter::emitForIn(ForNode*
         MOZ_ASSERT(
             forInTarget->isKind(ParseNodeKind::VarStmt),
             "for-in initializers are only permitted for |var| declarations");
 
         if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
           return false;
         }
 
-        RootedAtom nameAtom(cx, nameNode->name());
+        const ParserAtom* nameAtom = nameNode->name();
         NameOpEmitter noe(this, nameAtom, NameOpEmitter::Kind::Initialize);
         if (!noe.prepareForRhs()) {
           return false;
         }
         if (!emitInitializer(initializer, nameNode)) {
           return false;
         }
         if (!noe.emitAssignment()) {
@@ -5848,17 +5848,17 @@ bool BytecodeEmitter::emitWhile(BinaryNo
 
   if (!wh.emitEnd()) {
     return false;
   }
 
   return true;
 }
 
-bool BytecodeEmitter::emitBreak(PropertyName* label) {
+bool BytecodeEmitter::emitBreak(const ParserName* label) {
   BreakableControl* target;
   if (label) {
     // Any statement with the matching label may be the break target.
     auto hasSameLabel = [label](LabelControl* labelControl) {
       return labelControl->label() == label;
     };
     target = findInnermostNestableControl<LabelControl>(hasSameLabel);
   } else {
@@ -5866,17 +5866,17 @@ bool BytecodeEmitter::emitBreak(Property
       return !control->is<LabelControl>();
     };
     target = findInnermostNestableControl<BreakableControl>(isNotLabel);
   }
 
   return emitGoto(target, &target->breaks, GotoKind::Break);
 }
 
-bool BytecodeEmitter::emitContinue(PropertyName* label) {
+bool BytecodeEmitter::emitContinue(const ParserName* label) {
   LoopControl* target = nullptr;
   if (label) {
     // Find the loop statement enclosed by the matching label.
     NestableControl* control = innermostNestableControl;
     while (!control->is<LabelControl>() ||
            control->as<LabelControl>().label() != label) {
       if (control->is<LoopControl>()) {
         target = &control->as<LoopControl>();
@@ -7049,17 +7049,18 @@ bool BytecodeEmitter::emitDeleteElementI
   if (!eoe.emitDelete()) {
     //              [stack] SUCCEEDED
     return false;
   }
 
   return true;
 }
 
-static const char* SelfHostedCallFunctionName(JSAtom* name, JSContext* cx) {
+static const char* SelfHostedCallFunctionName(const ParserAtom* name,
+                                              JSContext* cx) {
   if (name == cx->parserNames().callFunction) {
     return "callFunction";
   }
   if (name == cx->parserNames().callContentFunction) {
     return "callContentFunction";
   }
   if (name == cx->parserNames().constructContentFunction) {
     return "constructContentFunction";
@@ -7170,17 +7171,17 @@ bool BytecodeEmitter::emitSelfHostedResu
   if (!emitTree(valNode)) {
     //              [stack] GENERATOR VALUE
     return false;
   }
 
   ParseNode* kindNode = valNode->pn_next;
   MOZ_ASSERT(kindNode->isKind(ParseNodeKind::StringExpr));
   GeneratorResumeKind kind =
-      AtomToResumeKind(cx, kindNode->as<NameNode>().atom());
+      ParserAtomToResumeKind(compilationInfo, kindNode->as<NameNode>().atom());
   MOZ_ASSERT(!kindNode->pn_next);
 
   if (!emitPushResumeKind(kind)) {
     //              [stack] GENERATOR VALUE RESUMEKIND
     return false;
   }
 
   if (!emit1(JSOp::Resume)) {
@@ -7341,17 +7342,17 @@ bool BytecodeEmitter::emitSelfHostedGetB
   ParseNode* argNode = argsList->head();
 
   if (!argNode->isKind(ParseNodeKind::StringExpr)) {
     reportError(callNode, JSMSG_UNEXPECTED_TYPE, "built-in name",
                 "not a string constant");
     return false;
   }
 
-  JSAtom* name = argNode->as<NameNode>().atom();
+  const ParserAtom* name = argNode->as<NameNode>().atom();
 
   BuiltinObjectKind kind;
   if (isConstructor) {
     kind = BuiltinConstructorForName(cx, name);
   } else {
     kind = BuiltinPrototypeForName(cx, name);
   }
 
@@ -7434,24 +7435,24 @@ bool BytecodeEmitter::isRestParameter(Pa
   }
 
   if (!expr->isKind(ParseNodeKind::Name)) {
     return allowSelfHostedIter(expr) &&
            isRestParameter(
                expr->as<BinaryNode>().right()->as<ListNode>().head());
   }
 
-  JSAtom* name = expr->as<NameNode>().name();
+  const ParserAtom* name = expr->as<NameNode>().name();
   Maybe<NameLocation> paramLoc = locationOfNameBoundInFunctionScope(name);
   if (paramLoc && lookupName(name) == *paramLoc) {
-    FunctionScope::Data* bindings = funbox->functionScopeBindings();
+    ParserFunctionScopeData* bindings = funbox->functionScopeBindings();
     if (bindings->nonPositionalFormalStart > 0) {
       // |paramName| can be nullptr when the rest destructuring syntax is
       // used: `function f(...[]) {}`.
-      JSAtom* paramName =
+      const ParserAtom* paramName =
           bindings->trailingNames[bindings->nonPositionalFormalStart - 1]
               .name();
       return paramName && name == paramName;
     }
   }
 
   return false;
 }
@@ -7469,17 +7470,17 @@ bool BytecodeEmitter::emitOptionalCallee
                                                 CallOrNewEmitter& cone,
                                                 OptionalEmitter& oe) {
   if (!CheckRecursionLimit(cx)) {
     return false;
   }
 
   switch (ParseNodeKind kind = callee->getKind()) {
     case ParseNodeKind::Name: {
-      RootedAtom nameAtom(cx, callee->as<NameNode>().name());
+      const ParserAtom* nameAtom = callee->as<NameNode>().name();
       if (!cone.emitNameCallee(nameAtom)) {
         //          [stack] CALLEE THIS
         return false;
       }
       break;
     }
 
     case ParseNodeKind::OptionalDotExpr: {
@@ -7565,17 +7566,17 @@ bool BytecodeEmitter::emitOptionalCallee
 
   return true;
 }
 
 bool BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call,
                                         CallOrNewEmitter& cone) {
   switch (callee->getKind()) {
     case ParseNodeKind::Name: {
-      RootedAtom nameAtom(cx, callee->as<NameNode>().name());
+      const ParserAtom* nameAtom = callee->as<NameNode>().name();
       if (!cone.emitNameCallee(nameAtom)) {
         //          [stack] CALLEE THIS
         return false;
       }
       break;
     }
     case ParseNodeKind::DotExpr: {
       MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
@@ -7860,17 +7861,17 @@ bool BytecodeEmitter::emitCallOrNew(
   ListNode* argsList = &callNode->right()->as<ListNode>();
   bool isSpread = JOF_OPTYPE(callNode->callOp()) == JOF_BYTE;
 
   if (calleeNode->isKind(ParseNodeKind::Name) &&
       emitterMode == BytecodeEmitter::SelfHosting && !isSpread) {
     // Calls to "forceInterpreter", "callFunction",
     // "callContentFunction", or "resumeGenerator" in self-hosted
     // code generate inline bytecode.
-    RootedPropertyName calleeName(cx, calleeNode->as<NameNode>().name());
+    const ParserName* calleeName = calleeNode->as<NameNode>().name();
     if (calleeName == cx->parserNames().callFunction ||
         calleeName == cx->parserNames().callContentFunction ||
         calleeName == cx->parserNames().constructContentFunction) {
       return emitSelfHostedCallFunction(callNode);
     }
     if (calleeName == cx->parserNames().resumeGenerator) {
       return emitSelfHostedResumeGenerator(callNode);
     }
@@ -8372,17 +8373,17 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
       return emitNameIncDec(incDec);
   }
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitLabeledStatement(
     const LabeledStatement* labeledStmt) {
-  RootedAtom name(cx, labeledStmt->label());
+  const ParserAtom* name = labeledStmt->label();
   LabelEmitter label(this);
 
   label.emitLabel(name);
 
   if (!emitTree(labeledStmt->statement())) {
     return false;
   }
   if (!label.emitEnd()) {
@@ -8522,20 +8523,19 @@ bool BytecodeEmitter::emitPropertyList(L
   size_t curStaticFieldKeyIndex = 0;
   for (ParseNode* propdef : obj->contents()) {
     if (propdef->is<ClassField>()) {
       MOZ_ASSERT(type == ClassBody);
       // Only handle computing field keys here: the .initializers lambda array
       // is created elsewhere.
       ClassField* field = &propdef->as<ClassField>();
       if (field->name().getKind() == ParseNodeKind::ComputedName) {
-        auto fieldKeysName = field->isStatic()
-                                 ? &JSAtomState::dotStaticFieldKeys
-                                 : &JSAtomState::dotFieldKeys;
-        HandlePropertyName fieldKeys = cx->parserNames().*fieldKeysName;
+        const ParserName* fieldKeys = field->isStatic()
+                                          ? cx->parserNames().dotStaticFieldKeys
+                                          : cx->parserNames().dotFieldKeys;
         if (!emitGetName(fieldKeys)) {
           //        [stack] CTOR? OBJ ARRAY
           return false;
         }
 
         ParseNode* nameExpr = field->name().as<UnaryNode>().kid();
 
         if (!emitTree(nameExpr, ValueUsage::WantValue, EMIT_LINENOTE,
@@ -8637,29 +8637,30 @@ bool BytecodeEmitter::emitPropertyList(L
 
     auto emitValue = [this, &key, &propVal, accessorType, &pe]() {
       //            [stack] CTOR? OBJ CTOR? KEY?
 
       if (propVal->isDirectRHSAnonFunction()) {
         if (key->isKind(ParseNodeKind::NumberExpr)) {
           MOZ_ASSERT(accessorType == AccessorType::None);
 
-          RootedAtom keyAtom(cx, key->as<NumericLiteral>().toAtom(cx));
+          const ParserAtom* keyAtom =
+              key->as<NumericLiteral>().toAtom(compilationInfo);
           if (!keyAtom) {
             return false;
           }
           if (!emitAnonymousFunctionWithName(propVal, keyAtom)) {
             //      [stack] CTOR? OBJ CTOR? KEY VAL
             return false;
           }
         } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
                    key->isKind(ParseNodeKind::StringExpr)) {
           MOZ_ASSERT(accessorType == AccessorType::None);
 
-          RootedAtom keyAtom(cx, key->as<NameNode>().atom());
+          const ParserAtom* keyAtom = key->as<NameNode>().atom();
           if (!emitAnonymousFunctionWithName(propVal, keyAtom)) {
             //      [stack] CTOR? OBJ CTOR? VAL
             return false;
           }
         } else {
           MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName) ||
                      key->isKind(ParseNodeKind::BigIntExpr));
 
@@ -8754,17 +8755,17 @@ bool BytecodeEmitter::emitPropertyList(L
         return false;
       }
 
       if (!emitValue()) {
         //          [stack] CTOR? OBJ CTOR? VAL
         return false;
       }
 
-      RootedAtom keyAtom(cx, key->as<NameNode>().atom());
+      const ParserAtom* keyAtom = key->as<NameNode>().atom();
 
       if (!pe.emitInit(accessorType, keyAtom)) {
         return false;
       }
 
       continue;
     }
 
@@ -8900,17 +8901,17 @@ bool BytecodeEmitter::emitDestructuringR
   data.writer().beginObject(flags);
 
   for (ParseNode* member : pattern->contents()) {
     if (member->isKind(ParseNodeKind::Spread)) {
       MOZ_ASSERT(!member->pn_next, "unexpected trailing element after spread");
       break;
     }
 
-    JSAtom* atom;
+    const ParserAtom* atom = nullptr;
     if (member->isKind(ParseNodeKind::MutateProto)) {
       atom = cx->parserNames().proto;
     } else {
       ParseNode* key = member->as<BinaryNode>().left();
       atom = key->as<NameNode>().atom();
     }
 
     uint32_t propNameIndex = 0;
@@ -9089,19 +9090,18 @@ bool BytecodeEmitter::emitCreateFieldKey
   };
 
   size_t numFieldKeys = std::count_if(
       obj->contents().begin(), obj->contents().end(), isFieldWithComputedName);
   if (numFieldKeys == 0) {
     return true;
   }
 
-  auto fieldKeysName =
-      isStatic ? &JSAtomState::dotStaticFieldKeys : &JSAtomState::dotFieldKeys;
-  HandlePropertyName fieldKeys = cx->parserNames().*fieldKeysName;
+  const ParserName* fieldKeys = isStatic ? cx->parserNames().dotStaticFieldKeys
+                                         : cx->parserNames().dotFieldKeys;
   NameOpEmitter noe(this, fieldKeys, NameOpEmitter::Kind::Initialize);
   if (!noe.prepareForRhs()) {
     return false;
   }
 
   if (!emitUint32Operand(JSOp::NewArray, numFieldKeys)) {
     //              [stack] ARRAY
     return false;
@@ -9241,17 +9241,18 @@ bool BytecodeEmitter::emitPrivateMethodI
       case AccessorType::Setter:
         if (!storedMethodName.append(".setter")) {
           return false;
         }
         break;
       default:
         MOZ_CRASH("Invalid private method accessor type");
     }
-    RootedAtom storedMethodAtom(cx, storedMethodName.finishAtom());
+    const ParserAtom* storedMethodAtom =
+        storedMethodName.finishParserAtom(compilationInfo);
 
     // Emit the private method body and store it as a lexical var.
     if (!emitFunction(&propdef->as<ClassMethod>().method())) {
       //              [stack] HOMEOBJ HERITAGE? ARRAY METHOD
       // or:
       //              [stack] CTOR HOMEOBJ ARRAY METHOD
       return false;
     }
@@ -9291,21 +9292,19 @@ bool BytecodeEmitter::emitPrivateMethodI
       //              [stack] CTOR HOMEOBJ ARRAY
       return false;
     }
   }
 
   return true;
 }
 
-bool BytecodeEmitter::emitPrivateMethodInitializer(ClassEmitter& ce,
-                                                   ParseNode* prop,
-                                                   ParseNode* propName,
-                                                   HandleAtom storedMethodAtom,
-                                                   AccessorType accessorType) {
+bool BytecodeEmitter::emitPrivateMethodInitializer(
+    ClassEmitter& ce, ParseNode* prop, ParseNode* propName,
+    const ParserAtom* storedMethodAtom, AccessorType accessorType) {
   // Emit the synthesized initializer function.
   FunctionNode* funNode = prop->as<ClassMethod>().initializerIfPrivate();
   if (!funNode) {
     return false;
   }
   FunctionBox* funbox = funNode->funbox();
   FunctionEmitter fe(this, funbox, funNode->syntaxKind(),
                      FunctionEmitter::IsHoisted::No);
@@ -9544,17 +9543,17 @@ bool BytecodeEmitter::emitInitializeStat
     if (!emit1(JSOp::Pop)) {
       //            [stack] CTOR ARRAY?
       return false;
     }
   }
 
   // Overwrite |.staticInitializers| and |.staticFieldKeys| with undefined to
   // avoid keeping the arrays alive indefinitely.
-  auto clearStaticFieldSlot = [&](HandlePropertyName name) {
+  auto clearStaticFieldSlot = [&](const ParserName* name) {
     NameOpEmitter noe(this, name, NameOpEmitter::Kind::SimpleAssignment);
     if (!noe.prepareForRhs()) {
       //            [stack] ENV? VAL?
       return false;
     }
 
     if (!emit1(JSOp::Undefined)) {
       //            [stack] ENV? VAL? UNDEFINED
@@ -9949,17 +9948,17 @@ bool BytecodeEmitter::emitFunctionFormal
           //        [stack]
           return false;
         }
         if (!fpe.emitDestructuringRestEnd()) {
           //        [stack]
           return false;
         }
       } else {
-        RootedAtom paramName(cx, bindingElement->as<NameNode>().name());
+        const ParserName* paramName = bindingElement->as<NameNode>().name();
         if (!fpe.emitRest(paramName)) {
           //        [stack]
           return false;
         }
       }
 
       continue;
     }
@@ -10008,66 +10007,66 @@ bool BytecodeEmitter::emitFunctionFormal
       if (!fpe.prepareForDefault()) {
         //          [stack]
         return false;
       }
       if (!emitDefaultInitializer()) {
         //          [stack]
         return false;
       }
-      RootedAtom paramName(cx, bindingElement->as<NameNode>().name());
+      const ParserAtom* paramName = bindingElement->as<NameNode>().name();
       if (!fpe.emitDefaultEnd(paramName)) {
         //          [stack]
         return false;
       }
 
       continue;
     }
 
-    RootedAtom paramName(cx, bindingElement->as<NameNode>().name());
+    const ParserAtom* paramName = bindingElement->as<NameNode>().name();
     if (!fpe.emitSimple(paramName)) {
       //            [stack]
       return false;
     }
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitInitializeFunctionSpecialNames() {
   FunctionBox* funbox = sc->asFunctionBox();
 
   //                [stack]
 
-  auto emitInitializeFunctionSpecialName =
-      [](BytecodeEmitter* bce, HandlePropertyName name, JSOp op) {
-        // A special name must be slotful, either on the frame or on the
-        // call environment.
-        MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
-
-        NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
-        if (!noe.prepareForRhs()) {
-          //        [stack]
-          return false;
-        }
-        if (!bce->emit1(op)) {
-          //        [stack] THIS/ARGUMENTS
-          return false;
-        }
-        if (!noe.emitAssignment()) {
-          //        [stack] THIS/ARGUMENTS
-          return false;
-        }
-        if (!bce->emit1(JSOp::Pop)) {
-          //        [stack]
-          return false;
-        }
-
-        return true;
-      };
+  auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce,
+                                              const ParserName* name, JSOp op) {
+    // A special name must be slotful, either on the frame or on the
+    // call environment.
+    MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
+
+    NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {
+      //        [stack]
+      return false;
+    }
+    if (!bce->emit1(op)) {
+      //        [stack] THIS/ARGUMENTS
+      return false;
+    }
+    if (!noe.emitAssignment()) {
+      //        [stack] THIS/ARGUMENTS
+      return false;
+    }
+    if (!bce->emit1(JSOp::Pop)) {
+      //        [stack]
+      return false;
+    }
+
+    return true;
+  };
 
   // Do nothing if the function doesn't have an arguments binding.
   if (funbox->argumentsHasVarBinding()) {
     if (!emitInitializeFunctionSpecialName(this, cx->parserNames().arguments,
                                            JSOp::Arguments)) {
       //            [stack]
       return false;
     }
@@ -10090,21 +10089,20 @@ bool BytecodeEmitter::emitInitializeFunc
       //            [stack]
       return false;
     }
   }
   return true;
 }
 
 bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) {
-  RootedAtom nameAtom(cx, name->name());
-  return emitLexicalInitialization(nameAtom);
-}
-
-bool BytecodeEmitter::emitLexicalInitialization(Handle<JSAtom*> name) {
+  return emitLexicalInitialization(name->name());
+}
+
+bool BytecodeEmitter::emitLexicalInitialization(const ParserAtom* name) {
   NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
   if (!noe.prepareForRhs()) {
     return false;
   }
 
   // The caller has pushed the RHS to the top of the stack. Assert that the
   // name is lexical and no BIND[G]NAME ops were emitted.
   MOZ_ASSERT(noe.loc().isLexical());
@@ -10145,17 +10143,17 @@ bool BytecodeEmitter::emitNewPrivateName
       continue;
     }
 
     ParseNode* elementName = &classElement->as<ClassMemberType>().name();
     if (!elementName->isKind(ParseNodeKind::PrivateName)) {
       continue;
     }
 
-    RootedAtom privateName(cx, elementName->as<NameNode>().name());
+    const ParserAtom* privateName = elementName->as<NameNode>().name();
 
     // TODO: Add a new bytecode to create private names.
     if (!emitAtomOp(JSOp::GetIntrinsic, cx->parserNames().NewPrivateName)) {
       //          [stack] HERITAGE NEWPRIVATENAME
       return false;
     }
 
     // Push `undefined` as `this` parameter for call.
@@ -10190,31 +10188,31 @@ bool BytecodeEmitter::emitNewPrivateName
   return true;
 }
 
 // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
 // (BindingClassDeclarationEvaluation).
 bool BytecodeEmitter::emitClass(
     ClassNode* classNode,
     ClassNameKind nameKind /* = ClassNameKind::BindingName */,
-    HandleAtom nameForAnonymousClass /* = nullptr */) {
+    const ParserAtom* nameForAnonymousClass /* = nullptr */) {
   MOZ_ASSERT((nameKind == ClassNameKind::InferredName) ==
-             (nameForAnonymousClass != nullptr));
+             bool(nameForAnonymousClass));
 
   ParseNode* heritageExpression = classNode->heritage();
   ListNode* classMembers = classNode->memberList();
   ParseNode* constructor = FindConstructor(cx, classMembers);
 
   // If |nameKind != ClassNameKind::ComputedName|
   //                [stack]
   // Else
   //                [stack] NAME
 
   ClassEmitter ce(this);
-  RootedAtom innerName(cx);
+  const ParserAtom* innerName = nullptr;
   ClassEmitter::Kind kind = ClassEmitter::Kind::Expression;
   if (ClassNames* names = classNode->names()) {
     MOZ_ASSERT(nameKind == ClassNameKind::BindingName);
     innerName = names->innerBinding()->name();
     MOZ_ASSERT(innerName);
 
     if (names->outerBinding()) {
       MOZ_ASSERT(names->outerBinding()->name());
@@ -10391,18 +10389,17 @@ bool BytecodeEmitter::emitClass(
 
 bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) {
   MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt));
 
   ParseNode* valueNode = exportNode->left();
   if (valueNode->isDirectRHSAnonFunction()) {
     MOZ_ASSERT(exportNode->right());
 
-    HandlePropertyName name = cx->parserNames().default_;
-    if (!emitAnonymousFunctionWithName(valueNode, name)) {
+    if (!emitAnonymousFunctionWithName(valueNode, cx->parserNames().default_)) {
       return false;
     }
   } else {
     if (!emitTree(valueNode)) {
       return false;
     }
   }
 
@@ -10456,17 +10453,18 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
   //            [stack] CALLBACK
 
   // Push undefined for the call's |this| value.
   if (!emit1(JSOp::Undefined)) {
     return false;
   }
   //            [stack] CALLBACK UNDEFINED
 
-  JSAtom* atom = RealmInstrumentation::getInstrumentationKindName(cx, kind);
+  const ParserAtom* atom =
+      RealmInstrumentation::getInstrumentationKindName(compilationInfo, kind);
   if (!atom) {
     return false;
   }
 
   if (!emitAtomOp(JSOp::String, atom)) {
     return false;
   }
   //            [stack] CALLBACK UNDEFINED KIND
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -204,49 +204,49 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool init(TokenPos bodyPosition);
 
   template <typename T>
   T* findInnermostNestableControl() const;
 
   template <typename T, typename Predicate /* (T*) -> bool */>
   T* findInnermostNestableControl(Predicate predicate) const;
 
-  NameLocation lookupName(JSAtom* name);
+  NameLocation lookupName(const ParserAtom* name);
 
   // To implement Annex B and the formal parameter defaults scope semantics
   // requires accessing names that would otherwise be shadowed. This method
   // returns the access location of a name that is known to be bound in a
   // target scope.
-  mozilla::Maybe<NameLocation> locationOfNameBoundInScope(JSAtom* name,
-                                                          EmitterScope* target);
+  mozilla::Maybe<NameLocation> locationOfNameBoundInScope(
+      const ParserAtom* name, EmitterScope* target);
 
   // Get the location of a name known to be bound in the function scope,
   // starting at the source scope.
   mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope(
-      JSAtom* name, EmitterScope* source);
+      const ParserAtom* name, EmitterScope* source);
 
   mozilla::Maybe<NameLocation> locationOfNameBoundInFunctionScope(
-      JSAtom* name) {
+      const ParserAtom* name) {
     return locationOfNameBoundInFunctionScope(name, innermostEmitterScope());
   }
 
   void setVarEmitterScope(EmitterScope* emitterScope) {
     MOZ_ASSERT(emitterScope);
     MOZ_ASSERT(!varEmitterScope);
     varEmitterScope = emitterScope;
   }
 
   AbstractScopePtr outermostScope() const {
     return perScriptData().gcThingList().firstScope();
   }
   AbstractScopePtr innermostScope() const;
   ScopeIndex innermostScopeIndex() const;
 
-  MOZ_ALWAYS_INLINE
-  MOZ_MUST_USE bool makeAtomIndex(JSAtom* atom, GCThingIndex* indexp) {
+  MOZ_ALWAYS_INLINE MOZ_MUST_USE bool makeAtomIndex(const ParserAtom* atom,
+                                                    GCThingIndex* indexp) {
     MOZ_ASSERT(perScriptData().atomIndices());
     AtomIndexMap::AddPtr p = perScriptData().atomIndices()->lookupForAdd(atom);
     if (p) {
       *indexp = GCThingIndex(p->value());
       return true;
     }
 
     GCThingIndex index;
@@ -451,17 +451,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
 
   enum class GotoKind { Break, Continue };
   MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist,
                              GotoKind kind);
 
   MOZ_MUST_USE bool emitGCIndexOp(JSOp op, GCThingIndex index);
 
   MOZ_MUST_USE bool emitAtomOp(
-      JSOp op, JSAtom* atom,
+      JSOp op, const ParserAtom* atom,
       ShouldInstrument shouldInstrument = ShouldInstrument::No);
   MOZ_MUST_USE bool emitAtomOp(
       JSOp op, GCThingIndex atomIndex,
       ShouldInstrument shouldInstrument = ShouldInstrument::No);
 
   MOZ_MUST_USE bool emitArrayLiteral(ListNode* array);
   MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count,
                               bool isInner = false);
@@ -513,59 +513,50 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                                  ListNode* obj,
                                                  FieldPlacement placement);
   const MemberInitializers& findMemberInitializersForCall();
   MOZ_MUST_USE bool emitInitializeInstanceMembers();
   MOZ_MUST_USE bool emitInitializeStaticFields(ListNode* classMembers);
 
   MOZ_MUST_USE bool emitPrivateMethodInitializers(ClassEmitter& ce,
                                                   ListNode* obj);
-  MOZ_MUST_USE bool emitPrivateMethodInitializer(ClassEmitter& ce,
-                                                 ParseNode* prop,
-                                                 ParseNode* propName,
-                                                 HandleAtom storedMethodAtom,
-                                                 AccessorType accessorType);
+  MOZ_MUST_USE bool emitPrivateMethodInitializer(
+      ClassEmitter& ce, ParseNode* prop, ParseNode* propName,
+      const ParserAtom* storedMethodAtom, AccessorType accessorType);
 
   // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
   // not used to unconditionally emit JSOp::GetLocal. Variable access should
   // instead be emitted using EmitVarOp. In special cases, when the caller
   // definitely knows that a given local slot is unaliased, this function may be
   // used as a non-asserting version of emitUint16Operand.
   MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
 
   MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
   MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
 
-  MOZ_MUST_USE bool emitGetNameAtLocation(Handle<JSAtom*> name,
+  MOZ_MUST_USE bool emitGetNameAtLocation(const ParserAtom* name,
                                           const NameLocation& loc);
-  MOZ_MUST_USE bool emitGetNameAtLocation(ImmutablePropertyNamePtr name,
-                                          const NameLocation& loc) {
-    return emitGetNameAtLocation(name.toHandle(), loc);
-  }
-  MOZ_MUST_USE bool emitGetName(Handle<JSAtom*> name) {
-    return emitGetNameAtLocation(name, lookupName(name));
-  }
-  MOZ_MUST_USE bool emitGetName(ImmutablePropertyNamePtr name) {
+  MOZ_MUST_USE bool emitGetName(const ParserAtom* name) {
     return emitGetNameAtLocation(name, lookupName(name));
   }
   MOZ_MUST_USE bool emitGetName(NameNode* name);
   MOZ_MUST_USE bool emitGetPrivateName(NameNode* name);
-  MOZ_MUST_USE bool emitGetPrivateName(JSAtom* name);
+  MOZ_MUST_USE bool emitGetPrivateName(const ParserAtom* name);
 
-  MOZ_MUST_USE bool emitTDZCheckIfNeeded(HandleAtom name,
+  MOZ_MUST_USE bool emitTDZCheckIfNeeded(const ParserAtom* name,
                                          const NameLocation& loc,
                                          ValueIsOnStack isOnStack);
 
   MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec);
 
   MOZ_MUST_USE bool emitDeclarationList(ListNode* declList);
   MOZ_MUST_USE bool emitSingleDeclaration(ListNode* declList, NameNode* decl,
                                           ParseNode* initializer);
   MOZ_MUST_USE bool emitAssignmentRhs(ParseNode* rhs,
-                                      HandleAtom anonFunctionName);
+                                      const ParserAtom* anonFunctionName);
   MOZ_MUST_USE bool emitAssignmentRhs(uint8_t offset);
 
   MOZ_MUST_USE bool emitPrepareIteratorResult();
   MOZ_MUST_USE bool emitFinishIteratorResult(bool done);
   MOZ_MUST_USE bool iteratorResultShape(GCThingIndex* shape);
 
   MOZ_MUST_USE bool emitGetDotGeneratorInInnermostScope() {
     return emitGetDotGeneratorInScope(*innermostEmitterScope());
@@ -690,22 +681,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   // 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 emitAnonymousFunctionWithName(ParseNode* node,
-                                                  JS::Handle<JSAtom*> name);
+                                                  const ParserAtom* name);
 
   MOZ_MUST_USE bool emitAnonymousFunctionWithComputedName(
       ParseNode* node, FunctionPrefixKind prefixKind);
 
-  MOZ_MUST_USE bool setFunName(FunctionBox* fun, JSAtom* name);
+  MOZ_MUST_USE bool setFunName(FunctionBox* fun, const ParserAtom* name);
   MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern);
 
   MOZ_MUST_USE bool emitCallSiteObjectArray(ListNode* cookedOrRaw,
                                             GCThingIndex* arrayIndex);
   MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
   MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
   MOZ_MUST_USE bool emitAssignmentOrInit(ParseNodeKind kind, ParseNode* lhs,
                                          ParseNode* rhs);
@@ -793,23 +784,23 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                   const EmitterScope* headLexicalEmitterScope);
   MOZ_MUST_USE bool emitForIn(ForNode* forNode,
                               const EmitterScope* headLexicalEmitterScope);
   MOZ_MUST_USE bool emitForOf(ForNode* forNode,
                               const EmitterScope* headLexicalEmitterScope);
 
   MOZ_MUST_USE bool emitInitializeForInOrOfTarget(TernaryNode* forHead);
 
-  MOZ_MUST_USE bool emitBreak(PropertyName* label);
-  MOZ_MUST_USE bool emitContinue(PropertyName* label);
+  MOZ_MUST_USE bool emitBreak(const ParserName* label);
+  MOZ_MUST_USE bool emitContinue(const ParserName* label);
 
   MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody);
   MOZ_MUST_USE bool emitInitializeFunctionSpecialNames();
   MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name);
-  MOZ_MUST_USE bool emitLexicalInitialization(Handle<JSAtom*> name);
+  MOZ_MUST_USE bool emitLexicalInitialization(const ParserAtom* name);
 
   // Emit bytecode for the spread operator.
   //
   // emitSpread expects the current index (I) of the array, the array itself
   // and the iterator to be on the stack in that order (iterator on the bottom).
   // It will pop the iterator and I, then iterate over the iterator by calling
   // |.next()| and put the results into the I-th element of array with
   // incrementing I, then push the result I (it will be original I +
@@ -824,17 +815,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
     InferredName,
 
     // The class is anonymous and has a dynamically computed name.
     ComputedName
   };
 
   MOZ_MUST_USE bool emitClass(
       ClassNode* classNode, ClassNameKind nameKind = ClassNameKind::BindingName,
-      JS::Handle<JSAtom*> nameForAnonymousClass = nullptr);
+      const ParserAtom* nameForAnonymousClass = nullptr);
+
   MOZ_MUST_USE bool emitSuperElemOperands(
       PropertyByValue* elem, EmitElemOption opts = EmitElemOption::Get);
   MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem,
                                      bool isCall = false);
 
   MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call,
                                       CallOrNewEmitter& cone);
 
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -53,18 +53,22 @@ bool js::frontend::EmitScriptThingsVecto
 
   struct Matcher {
     JSContext* cx;
     CompilationInfo& compilationInfo;
     uint32_t i;
     mozilla::Span<JS::GCCellPtr>& output;
 
     bool operator()(const ScriptAtom& data) {
-      JSAtom* atom = data;
-      output[i] = JS::GCCellPtr(atom);
+      auto maybeAtom = data->toJSAtom(cx);
+      if (maybeAtom.isErr()) {
+        return false;
+      }
+      MOZ_ASSERT(maybeAtom.unwrap());
+      output[i] = JS::GCCellPtr(maybeAtom.unwrap());
       return true;
     }
 
     bool operator()(const NullScriptThing& data) {
       output[i] = JS::GCCellPtr(nullptr);
       return true;
     }
 
@@ -85,17 +89,17 @@ bool js::frontend::EmitScriptThingsVecto
         return false;
       }
       output[i] = JS::GCCellPtr(regexp);
       return true;
     }
 
     bool operator()(const ObjLiteralIndex& index) {
       ObjLiteralStencil& data = compilationInfo.objLiteralData[index];
-      JSObject* obj = data.create(cx);
+      JSObject* obj = data.create(cx, compilationInfo);
       if (!obj) {
         return false;
       }
       output[i] = JS::GCCellPtr(obj);
       return true;
     }
 
     bool operator()(const ScopeIndex& index) {
@@ -158,18 +162,19 @@ void CGScopeNoteList::recordEndFunctionB
 
 void CGScopeNoteList::recordEndImpl(uint32_t index, uint32_t offset) {
   MOZ_ASSERT(index < length());
   MOZ_ASSERT(list[index].length == 0);
   MOZ_ASSERT(offset >= list[index].start);
   list[index].length = offset - list[index].start;
 }
 
-JSObject* ObjLiteralStencil::create(JSContext* cx) const {
-  return InterpretObjLiteral(cx, atoms_, writer_);
+JSObject* ObjLiteralStencil::create(JSContext* cx,
+                                    CompilationInfo& compilationInfo) const {
+  return InterpretObjLiteral(cx, compilationInfo, atoms_, writer_);
 }
 
 BytecodeSection::BytecodeSection(JSContext* cx, uint32_t lineNum)
     : code_(cx),
       notes_(cx),
       lastNoteOffset_(0),
       tryNoteList_(cx),
       scopeNoteList_(cx),
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -50,17 +50,17 @@ struct MOZ_STACK_CLASS GCThingList {
   ScriptThingsVector vector;
 
   // Index of the first scope in the vector.
   mozilla::Maybe<GCThingIndex> firstScopeIndex;
 
   explicit GCThingList(JSContext* cx, CompilationInfo& compilationInfo)
       : compilationInfo(compilationInfo), vector(cx) {}
 
-  MOZ_MUST_USE bool append(JSAtom* atom, GCThingIndex* index) {
+  MOZ_MUST_USE bool append(const ParserAtom* atom, GCThingIndex* index) {
     *index = GCThingIndex(vector.length());
     return vector.append(mozilla::AsVariant(std::move(atom)));
   }
   MOZ_MUST_USE bool append(ScopeIndex scope, GCThingIndex* index) {
     *index = GCThingIndex(vector.length());
     if (!vector.append(mozilla::AsVariant(scope))) {
       return false;
     }
--- a/js/src/frontend/CallOrNewEmitter.cpp
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -33,17 +33,17 @@ CallOrNewEmitter::CallOrNewEmitter(Bytec
     : bce_(bce), op_(op), argumentsKind_(argumentsKind) {
   if (op_ == JSOp::Call && valueUsage == ValueUsage::IgnoreValue) {
     op_ = JSOp::CallIgnoresRv;
   }
 
   MOZ_ASSERT(isCall() || isNew() || isSuperCall());
 }
 
-bool CallOrNewEmitter::emitNameCallee(Handle<JSAtom*> name) {
+bool CallOrNewEmitter::emitNameCallee(const ParserAtom* name) {
   MOZ_ASSERT(state_ == State::Start);
 
   NameOpEmitter noe(
       bce_, name,
       isCall() ? NameOpEmitter::Kind::Call : NameOpEmitter::Kind::Get);
   if (!noe.emitGet()) {
     //              [stack] CALLEE THIS
     return false;
--- a/js/src/frontend/CallOrNewEmitter.h
+++ b/js/src/frontend/CallOrNewEmitter.h
@@ -285,17 +285,17 @@ class MOZ_STACK_CLASS CallOrNewEmitter {
 
   MOZ_MUST_USE bool isSpread() const { return JOF_OPTYPE(op_) == JOF_BYTE; }
 
   MOZ_MUST_USE bool isSingleSpreadRest() const {
     return argumentsKind_ == ArgumentsKind::SingleSpreadRest;
   }
 
  public:
-  MOZ_MUST_USE bool emitNameCallee(Handle<JSAtom*> name);
+  MOZ_MUST_USE bool emitNameCallee(const ParserAtom* name);
   MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp);
   MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem,
                                                    bool isPrivateElem);
   MOZ_MUST_USE bool prepareForFunctionCallee();
   MOZ_MUST_USE bool emitSuperCallee();
   MOZ_MUST_USE bool prepareForOtherCallee();
 
   MOZ_MUST_USE bool emitThis();
--- a/js/src/frontend/EmitterScope.cpp
+++ b/js/src/frontend/EmitterScope.cpp
@@ -29,17 +29,17 @@ EmitterScope::EmitterScope(BytecodeEmitt
       scopeIndex_(ScopeNote::NoScopeIndex),
       noteIndex_(ScopeNote::NoScopeNoteIndex) {}
 
 bool EmitterScope::ensureCache(BytecodeEmitter* bce) {
   return nameCache_.acquire(bce->cx);
 }
 
 bool EmitterScope::checkSlotLimits(BytecodeEmitter* bce,
-                                   const BindingIter& bi) {
+                                   const ParserBindingIter& bi) {
   if (bi.nextFrameSlot() >= LOCALNO_LIMIT ||
       bi.nextEnvironmentSlot() >= ENVCOORD_SLOT_LIMIT) {
     bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS);
     return false;
   }
   return true;
 }
 
@@ -56,41 +56,41 @@ bool EmitterScope::checkEnvironmentChain
     return false;
   }
 
   environmentChainLength_ = mozilla::AssertedCast<uint8_t>(hops + 1);
   return true;
 }
 
 void EmitterScope::updateFrameFixedSlots(BytecodeEmitter* bce,
-                                         const BindingIter& bi) {
+                                         const ParserBindingIter& bi) {
   nextFrameSlot_ = bi.nextFrameSlot();
   if (nextFrameSlot_ > bce->maxFixedSlots) {
     bce->maxFixedSlots = nextFrameSlot_;
   }
   MOZ_ASSERT_IF(
       bce->sc->isFunctionBox() && (bce->sc->asFunctionBox()->isGenerator() ||
                                    bce->sc->asFunctionBox()->isAsync()),
       bce->maxFixedSlots == 0);
 }
 
-bool EmitterScope::putNameInCache(BytecodeEmitter* bce, JSAtom* name,
+bool EmitterScope::putNameInCache(BytecodeEmitter* bce, const ParserAtom* name,
                                   NameLocation loc) {
   NameLocationMap& cache = *nameCache_;
   NameLocationMap::AddPtr p = cache.lookupForAdd(name);
   MOZ_ASSERT(!p);
   if (!cache.add(p, name, loc)) {
     ReportOutOfMemory(bce->cx);
     return false;
   }
   return true;
 }
 
 Maybe<NameLocation> EmitterScope::lookupInCache(BytecodeEmitter* bce,
-                                                JSAtom* name) {
+                                                const ParserAtom* name) {
   if (NameLocationMap::Ptr p = nameCache_->lookup(name)) {
     return Some(p->value().wrapped);
   }
   if (fallbackFreeNameLocation_ && nameCanBeFree(bce, name)) {
     return fallbackFreeNameLocation_;
   }
   return Nothing();
 }
@@ -123,17 +123,17 @@ mozilla::Maybe<ScopeIndex> EmitterScope:
   }
 
   // The enclosing script is already compiled or the current script is the
   // global script.
   return mozilla::Nothing();
 }
 
 /* static */
-bool EmitterScope::nameCanBeFree(BytecodeEmitter* bce, JSAtom* name) {
+bool EmitterScope::nameCanBeFree(BytecodeEmitter* bce, const ParserAtom* name) {
   // '.generator' cannot be accessed by name.
   return name != bce->cx->parserNames().dotGenerator;
 }
 
 #ifdef DEBUG
 static bool NameIsOnEnvironment(Scope* scope, JSAtom* name) {
   for (BindingIter bi(scope); bi; bi++) {
     // If found, the name must already be on the environment or an import,
@@ -163,16 +163,19 @@ static bool NameIsOnEnvironment(Scope* s
   // If not found, assume it's on the global or dynamically accessed.
   return true;
 }
 #endif
 
 /* static */
 NameLocation EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope,
                                                   uint8_t hops) {
+  // TODO-Stencil
+  //   This needs to be handled properly by snapshotting enclosing scopes.
+
   for (ScopeIter si(scope); si; si++) {
     MOZ_ASSERT(NameIsOnEnvironment(si.scope(), name));
 
     bool hasEnv = si.hasSyntacticEnvironment();
 
     switch (si.kind()) {
       case ScopeKind::Function:
         if (hasEnv) {
@@ -279,17 +282,18 @@ NameLocation EmitterScope::searchInEnclo
       MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
       hops++;
     }
   }
 
   MOZ_CRASH("Malformed scope chain");
 }
 
-NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce, JSAtom* name) {
+NameLocation EmitterScope::searchAndCache(BytecodeEmitter* bce,
+                                          const ParserAtom* name) {
   Maybe<NameLocation> loc;
   uint8_t hops = hasEnvironment() ? 1 : 0;
   DebugOnly<bool> inCurrentScript = enclosingInFrame();
 
   // Start searching in the current compilation.
   for (EmitterScope* es = enclosing(&bce); es; es = es->enclosing(&bce)) {
     loc = es->lookupInCache(bce, name);
     if (loc) {
@@ -308,19 +312,35 @@ NameLocation EmitterScope::searchAndCach
       inCurrentScript = false;
     }
 #endif
   }
 
   // If the name is not found in the current compilation, walk the Scope
   // chain encompassing the compilation.
   if (!loc) {
+    // TODO-Stencil
+    //   Here, we convert our name into a JSAtom*, and hard-crash on failure
+    //   to allocate.  This conversion should not be required as we should be
+    //   able to iterate up snapshotted scope chains that use parser atoms.
+    //
+    //   This will be fixed when parser scopes are snapshotted, and
+    //   `searchInEnclosingScope` changes to accepting a `const ParserAtom*`
+    //   instead of a `JSAtom*`.
+    //
+    //   See bug 1660275.
+    AutoEnterOOMUnsafeRegion oomUnsafe;
+    JSAtom* jsname = bce->compilationInfo.liftParserAtomToJSAtom(name);
+    if (!jsname) {
+      oomUnsafe.crash("EmitterScope::searchAndCache");
+    }
+
     inCurrentScript = false;
-    loc = Some(searchInEnclosingScope(name, bce->compilationInfo.enclosingScope,
-                                      hops));
+    loc = Some(searchInEnclosingScope(
+        jsname, bce->compilationInfo.enclosingScope, hops));
   }
 
   // Each script has its own frame. A free name that is accessed
   // from an inner script must not be a frame slot access. If this
   // assertion is hit, it is a bug in the free name analysis in the
   // parser.
   MOZ_ASSERT_IF(!inCurrentScript, loc->kind() != NameLocation::Kind::FrameSlot);
 
@@ -408,17 +428,17 @@ bool EmitterScope::deadZoneFrameSlotRang
 
 void EmitterScope::dump(BytecodeEmitter* bce) {
   fprintf(stdout, "EmitterScope [%s] %p\n", ScopeKindString(scope(bce).kind()),
           this);
 
   for (NameLocationMap::Range r = nameCache_->all(); !r.empty(); r.popFront()) {
     const NameLocation& l = r.front().value();
 
-    UniqueChars bytes = AtomToPrintableString(bce->cx, r.front().key());
+    UniqueChars bytes = ParserAtomToPrintableString(bce->cx, r.front().key());
     if (!bytes) {
       return;
     }
     if (l.kind() != NameLocation::Kind::Dynamic) {
       fprintf(stdout, "  %s %s ", BindingKindString(l.bindingKind()),
               bytes.get());
     } else {
       fprintf(stdout, "  %s ", bytes.get());
@@ -456,29 +476,29 @@ void EmitterScope::dump(BytecodeEmitter*
         break;
     }
   }
 
   fprintf(stdout, "\n");
 }
 
 bool EmitterScope::enterLexical(BytecodeEmitter* bce, ScopeKind kind,
-                                Handle<LexicalScope::Data*> bindings) {
+                                ParserLexicalScopeData* bindings) {
   MOZ_ASSERT(kind != ScopeKind::NamedLambda &&
              kind != ScopeKind::StrictNamedLambda);
   MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
 
   if (!ensureCache(bce)) {
     return false;
   }
 
   // Resolve bindings.
   TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
   uint32_t firstFrameSlot = frameSlotStart();
-  BindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
+  ParserBindingIter bi(*bindings, firstFrameSlot, /* isNamedLambda = */ false);
   for (; bi; bi++) {
     if (!checkSlotLimits(bce, bi)) {
       return false;
     }
 
     NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
     if (!putNameInCache(bce, bi.name(), loc)) {
       return false;
@@ -529,18 +549,18 @@ bool EmitterScope::enterLexical(Bytecode
 bool EmitterScope::enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox) {
   MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
   MOZ_ASSERT(funbox->namedLambdaBindings());
 
   if (!ensureCache(bce)) {
     return false;
   }
 
-  BindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT,
-                 /* isNamedLambda = */ true);
+  ParserBindingIter bi(*funbox->namedLambdaBindings(), LOCALNO_LIMIT,
+                       /* isNamedLambda = */ true);
   MOZ_ASSERT(bi.kind() == BindingKind::NamedLambdaCallee);
 
   // The lambda name, if not closed over, is accessed via JSOp::Callee and
   // not a frame slot. Do not update frame slot information.
   NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
   if (!putNameInCache(bce, bi.name(), loc)) {
     return false;
   }
@@ -577,17 +597,17 @@ bool EmitterScope::enterFunction(Bytecod
     return false;
   }
 
   // Resolve body-level bindings, if there are any.
   auto bindings = funbox->functionScopeBindings();
   if (bindings) {
     NameLocationMap& cache = *nameCache_;
 
-    BindingIter bi(*bindings, funbox->hasParameterExprs);
+    ParserBindingIter bi(*bindings, funbox->hasParameterExprs);
     for (; bi; bi++) {
       if (!checkSlotLimits(bce, bi)) {
         return false;
       }
 
       NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
       NameLocationMap::AddPtr p = cache.lookupForAdd(bi.name());
 
@@ -619,17 +639,17 @@ bool EmitterScope::enterFunction(Bytecod
   if (funbox->funHasExtensibleScope()) {
     fallbackFreeNameLocation_ = Some(NameLocation::Dynamic());
   }
 
   // In case of parameter expressions, the parameters are lexical
   // bindings and have TDZ.
   if (funbox->hasParameterExprs && nextFrameSlot_) {
     uint32_t paramFrameSlotEnd = 0;
-    for (BindingIter bi(*bindings, true); bi; bi++) {
+    for (ParserBindingIter bi(*bindings, true); bi; bi++) {
       if (!BindingKindIsLexical(bi.kind())) {
         break;
       }
 
       NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
       if (loc.kind() == NameLocation::Kind::FrameSlot) {
         MOZ_ASSERT(paramFrameSlotEnd <= loc.frameSlot());
         paramFrameSlotEnd = loc.frameSlot() + 1;
@@ -670,17 +690,17 @@ bool EmitterScope::enterFunctionExtraBod
 
   if (!ensureCache(bce)) {
     return false;
   }
 
   // Resolve body-level bindings, if there are any.
   uint32_t firstFrameSlot = frameSlotStart();
   if (auto bindings = funbox->extraVarScopeBindings()) {
-    BindingIter bi(*bindings, firstFrameSlot);
+    ParserBindingIter bi(*bindings, firstFrameSlot);
     for (; bi; bi++) {
       if (!checkSlotLimits(bce, bi)) {
         return false;
       }
 
       NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
       if (!putNameInCache(bce, bi.name(), loc)) {
         return false;
@@ -723,23 +743,23 @@ bool EmitterScope::enterFunctionExtraBod
   // The extra var scope needs a note to be mapped from a pc.
   if (!appendScopeNote(bce)) {
     return false;
   }
 
   return checkEnvironmentChainLength(bce);
 }
 
-class DynamicBindingIter : public BindingIter {
+class DynamicBindingIter : public ParserBindingIter {
  public:
   explicit DynamicBindingIter(GlobalSharedContext* sc)
-      : BindingIter(*sc->bindings) {}
+      : ParserBindingIter(*sc->bindings) {}
 
   explicit DynamicBindingIter(EvalSharedContext* sc)
-      : BindingIter(*sc->bindings, /* strict = */ false) {
+      : ParserBindingIter(*sc->bindings, /* strict = */ false) {
     MOZ_ASSERT(!sc->strict());
   }
 
   JSOp bindingOp() const {
     switch (kind()) {
       case BindingKind::Var:
         return JSOp::DefVar;
       case BindingKind::Let:
@@ -751,16 +771,21 @@ class DynamicBindingIter : public Bindin
     }
   }
 };
 
 bool EmitterScope::enterGlobal(BytecodeEmitter* bce,
                                GlobalSharedContext* globalsc) {
   MOZ_ASSERT(this == bce->innermostEmitterScopeNoCheck());
 
+  // TODO-Stencil
+  //   This is another snapshot-sensitive location.
+  //   The incoming atoms from the global scope object should be snapshotted.
+  //   For now, converting them to ParserAtoms here individually.
+
   bce->setVarEmitterScope(this);
 
   if (!ensureCache(bce)) {
     return false;
   }
 
   if (bce->emitterMode == BytecodeEmitter::SelfHosting) {
     // In self-hosting, it is incorrect to consult the global scope because
@@ -795,17 +820,17 @@ bool EmitterScope::enterGlobal(BytecodeE
   if (globalsc->bindings) {
     // Check for declaration conflicts before the Def* ops.
     if (!bce->emit1(JSOp::CheckGlobalOrEvalDecl)) {
       return false;
     }
 
     for (DynamicBindingIter bi(globalsc); bi; bi++) {
       NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
-      JSAtom* name = bi.name();
+      const ParserAtom* name = bi.name();
       if (!putNameInCache(bce, name, loc)) {
         return false;
       }
 
       // Define the name in the prologue. Do not emit DefVar for
       // functions that we'll emit DefFun for.
       if (bi.isTopLevelFunction()) {
         continue;
@@ -905,18 +930,18 @@ bool EmitterScope::enterModule(BytecodeE
 
   if (!ensureCache(bce)) {
     return false;
   }
 
   // Resolve body-level bindings, if there are any.
   TDZCheckCache* tdzCache = bce->innermostTDZCheckCache;
   Maybe<uint32_t> firstLexicalFrameSlot;
-  if (ModuleScope::Data* bindings = modulesc->bindings) {
-    BindingIter bi(*bindings);
+  if (ParserModuleScopeData* bindings = modulesc->bindings) {
+    ParserBindingIter bi(*bindings);
     for (; bi; bi++) {
       if (!checkSlotLimits(bce, bi)) {
         return false;
       }
 
       NameLocation loc = NameLocation::fromBinding(bi.kind(), bi.location());
       if (!putNameInCache(bce, bi.name(), loc)) {
         return false;
@@ -1062,24 +1087,25 @@ AbstractScopePtr EmitterScope::scope(con
   return bce->perScriptData().gcThingList().getScope(index());
 }
 
 mozilla::Maybe<ScopeIndex> EmitterScope::scopeIndex(
     const BytecodeEmitter* bce) const {
   return bce->perScriptData().gcThingList().getScopeIndex(index());
 }
 
-NameLocation EmitterScope::lookup(BytecodeEmitter* bce, JSAtom* name) {
+NameLocation EmitterScope::lookup(BytecodeEmitter* bce,
+                                  const ParserAtom* name) {
   if (Maybe<NameLocation> loc = lookupInCache(bce, name)) {
     return *loc;
   }
   return searchAndCache(bce, name);
 }
 
-Maybe<NameLocation> EmitterScope::locationBoundInScope(JSAtom* name,
+Maybe<NameLocation> EmitterScope::locationBoundInScope(const ParserAtom* name,
                                                        EmitterScope* target) {
   // The target scope must be an intra-frame enclosing scope of this
   // one. Count the number of extra hops to reach it.
   uint8_t extraHops = 0;
   for (EmitterScope* es = this; es != target; es = es->enclosingInFrame()) {
     if (es->hasEnvironment()) {
       extraHops++;
     }
--- a/js/src/frontend/EmitterScope.h
+++ b/js/src/frontend/EmitterScope.h
@@ -61,37 +61,37 @@ class EmitterScope : public Nestable<Emi
 
   // If kind is Lexical, Catch, or With, the index in the BytecodeEmitter's
   // block scope note list. Otherwise ScopeNote::NoScopeNote.
   uint32_t noteIndex_;
 
   MOZ_MUST_USE bool ensureCache(BytecodeEmitter* bce);
 
   MOZ_MUST_USE bool checkSlotLimits(BytecodeEmitter* bce,
-                                    const BindingIter& bi);
+                                    const ParserBindingIter& bi);
 
   MOZ_MUST_USE bool checkEnvironmentChainLength(BytecodeEmitter* bce);
 
-  void updateFrameFixedSlots(BytecodeEmitter* bce, const BindingIter& bi);
+  void updateFrameFixedSlots(BytecodeEmitter* bce, const ParserBindingIter& bi);
 
-  MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, JSAtom* name,
+  MOZ_MUST_USE bool putNameInCache(BytecodeEmitter* bce, const ParserAtom* name,
                                    NameLocation loc);
 
   mozilla::Maybe<NameLocation> lookupInCache(BytecodeEmitter* bce,
-                                             JSAtom* name);
+                                             const ParserAtom* name);
 
   EmitterScope* enclosing(BytecodeEmitter** bce) const;
 
   mozilla::Maybe<ScopeIndex> enclosingScopeIndex(BytecodeEmitter* bce) const;
 
-  static bool nameCanBeFree(BytecodeEmitter* bce, JSAtom* name);
+  static bool nameCanBeFree(BytecodeEmitter* bce, const ParserAtom* name);
 
   static NameLocation searchInEnclosingScope(JSAtom* name, Scope* scope,
                                              uint8_t hops);
-  NameLocation searchAndCache(BytecodeEmitter* bce, JSAtom* name);
+  NameLocation searchAndCache(BytecodeEmitter* bce, const ParserAtom* name);
 
   MOZ_MUST_USE bool internEmptyGlobalScopeAsBody(BytecodeEmitter* bce);
 
   template <typename ScopeCreator>
   MOZ_MUST_USE bool internScopeCreationData(BytecodeEmitter* bce,
                                             ScopeCreator createScope);
 
   template <typename ScopeCreator>
@@ -104,17 +104,17 @@ class EmitterScope : public Nestable<Emi
                                            uint32_t slotEnd) const;
 
  public:
   explicit EmitterScope(BytecodeEmitter* bce);
 
   void dump(BytecodeEmitter* bce);
 
   MOZ_MUST_USE bool enterLexical(BytecodeEmitter* bce, ScopeKind kind,
-                                 Handle<LexicalScope::Data*> bindings);
+                                 ParserLexicalScopeData* bindings);
   MOZ_MUST_USE bool enterNamedLambda(BytecodeEmitter* bce, FunctionBox* funbox);
   MOZ_MUST_USE bool enterFunction(BytecodeEmitter* bce, FunctionBox* funbox);
   MOZ_MUST_USE bool enterFunctionExtraBodyVar(BytecodeEmitter* bce,
                                               FunctionBox* funbox);
   MOZ_MUST_USE bool enterGlobal(BytecodeEmitter* bce,
                                 GlobalSharedContext* globalsc);
   MOZ_MUST_USE bool enterEval(BytecodeEmitter* bce, EvalSharedContext* evalsc);
   MOZ_MUST_USE bool enterModule(BytecodeEmitter* module,
@@ -147,18 +147,18 @@ class EmitterScope : public Nestable<Emi
 
   // The last frame slot used + 1.
   uint32_t frameSlotEnd() const { return nextFrameSlot_; }
 
   EmitterScope* enclosingInFrame() const {
     return Nestable<EmitterScope>::enclosing();
   }
 
-  NameLocation lookup(BytecodeEmitter* bce, JSAtom* name);
+  NameLocation lookup(BytecodeEmitter* bce, const ParserAtom* name);
 
-  mozilla::Maybe<NameLocation> locationBoundInScope(JSAtom* name,
+  mozilla::Maybe<NameLocation> locationBoundInScope(const ParserAtom* name,
                                                     EmitterScope* target);
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_EmitterScope_h */
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -2,25 +2,27 @@
  * 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/FoldConstants.h"
 
 #include "mozilla/FloatingPoint.h"
+#include "mozilla/Range.h"
 
 #include "jslibmath.h"
 #include "jsnum.h"
 
 #include "frontend/ParseNode.h"
 #include "frontend/ParseNodeVisitor.h"
 #include "frontend/Parser.h"
 #include "js/Conversions.h"
 #include "js/friend/StackLimits.h"  // js::CheckRecursionLimit
+#include "js/Vector.h"
 #include "vm/StringType.h"
 
 using namespace js;
 using namespace js::frontend;
 
 using JS::GenericNaN;
 using JS::ToInt32;
 using JS::ToUint32;
@@ -457,29 +459,30 @@ restart:
  */
 static bool FoldType(FoldInfo info, ParseNode** pnp, ParseNodeKind kind) {
   const ParseNode* pn = *pnp;
   if (!pn->isKind(kind)) {
     switch (kind) {
       case ParseNodeKind::NumberExpr:
         if (pn->isKind(ParseNodeKind::StringExpr)) {
           double d;
-          if (!StringToNumber(info.cx(), pn->as<NameNode>().atom(), &d)) {
+          if (!pn->as<NameNode>().atom()->toNumber(info.cx(), &d)) {
             return false;
           }
           if (!TryReplaceNode(
                   pnp, info.handler->newNumber(d, NoDecimal, pn->pn_pos))) {
             return false;
           }
         }
         break;
 
       case ParseNodeKind::StringExpr:
         if (pn->isKind(ParseNodeKind::NumberExpr)) {
-          JSAtom* atom = pn->as<NumericLiteral>().toAtom(info.cx());
+          const ParserAtom* atom =
+              pn->as<NumericLiteral>().toAtom(info.compilationInfo);
           if (!atom) {
             return false;
           }
           if (!TryReplaceNode(
                   pnp, info.handler->newStringLiteral(atom, pn->pn_pos))) {
             return false;
           }
         }
@@ -571,31 +574,31 @@ static bool SimplifyCondition(FoldInfo i
 }
 
 static bool FoldTypeOfExpr(FoldInfo info, ParseNode** nodePtr) {
   UnaryNode* node = &(*nodePtr)->as<UnaryNode>();
   MOZ_ASSERT(node->isKind(ParseNodeKind::TypeOfExpr));
   ParseNode* expr = node->kid();
 
   // Constant-fold the entire |typeof| if given a constant with known type.
-  RootedPropertyName result(info.cx());
+  const ParserName* result = nullptr;
   if (expr->isKind(ParseNodeKind::StringExpr) ||
       expr->isKind(ParseNodeKind::TemplateStringExpr)) {
-    result = info.cx()->names().string;
+    result = info.cx()->parserNames().string;
   } else if (expr->isKind(ParseNodeKind::NumberExpr)) {
-    result = info.cx()->names().number;
+    result = info.cx()->parserNames().number;
   } else if (expr->isKind(ParseNodeKind::BigIntExpr)) {
-    result = info.cx()->names().bigint;
+    result = info.cx()->parserNames().bigint;
   } else if (expr->isKind(ParseNodeKind::NullExpr)) {
-    result = info.cx()->names().object;
+    result = info.cx()->parserNames().object;
   } else if (expr->isKind(ParseNodeKind::TrueExpr) ||
              expr->isKind(ParseNodeKind::FalseExpr)) {
-    result = info.cx()->names().boolean;
+    result = info.cx()->parserNames().boolean;
   } else if (expr->is<FunctionNode>()) {
-    result = info.cx()->names().function;
+    result = info.cx()->parserNames().function;
   }
 
   if (result) {
     if (!TryReplaceNode(nodePtr,
                         info.handler->newStringLiteral(result, node->pn_pos))) {
       return false;
     }
   }
@@ -1076,45 +1079,45 @@ static bool FoldExponentiation(FoldInfo 
                                      ecmaPow(d1, d2), NoDecimal, node->pn_pos));
 }
 
 static bool FoldElement(FoldInfo info, ParseNode** nodePtr) {
   PropertyByValue* elem = &(*nodePtr)->as<PropertyByValue>();
 
   ParseNode* expr = &elem->expression();
   ParseNode* key = &elem->key();
-  PropertyName* name = nullptr;
+  const ParserName* name = nullptr;
   if (key->isKind(ParseNodeKind::StringExpr)) {
-    JSAtom* atom = key->as<NameNode>().atom();
+    const ParserAtom* atom = key->as<NameNode>().atom();
     uint32_t index;
 
     if (atom->isIndex(&index)) {
       // Optimization 1: We have something like expr["100"]. This is
       // equivalent to expr[100] which is faster.
       if (!TryReplaceNode(
               elem->unsafeRightReference(),
               info.handler->newNumber(index, NoDecimal, key->pn_pos))) {
         return false;
       }
       key = &elem->key();
     } else {
-      name = atom->asPropertyName();
+      name = atom->asName();
     }
   } else if (key->isKind(ParseNodeKind::NumberExpr)) {
     auto* numeric = &key->as<NumericLiteral>();
     double number = numeric->value();
     if (number != ToUint32(number)) {
       // Optimization 2: We have something like expr[3.14]. The number
       // isn't an array index, so it converts to a string ("3.14"),
       // enabling optimization 3 below.
-      JSAtom* atom = numeric->toAtom(info.cx());
+      const ParserAtom* atom = numeric->toAtom(info.compilationInfo);
       if (!atom) {
         return false;
       }
-      name = atom->asPropertyName();
+      name = atom->asName();
     }
   }
 
   // If we don't have a name, we can't optimize to getprop.
   if (!name) {
     return true;
   }
 
@@ -1197,57 +1200,63 @@ static bool FoldAdd(FoldInfo info, Parse
       next = &(*current)->pn_next;
     } while (*next);
 
     // If there's nothing left to fold, we're done.
     if (!*next) {
       break;
     }
 
-    RootedString combination(info.cx());
-    RootedString tmp(info.cx());
+    Vector<const ParserAtom*, 8> accum(info.cx());
     do {
-      // Create a rope of the current string and all succeeding
-      // constants that we can convert to strings, then atomize it
-      // and replace them all with that fresh string.
+      // Create a vector of all the folded strings and concatenate them.
       MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
 
-      combination = (*current)->as<NameNode>().atom();
+      accum.clear();
+      const ParserAtom* atom = (*current)->as<NameNode>().atom();
+      if (!accum.append(atom)) {
+        return false;
+      }
 
       do {
         // Try folding the next operand to a string.
         if (!FoldType(info, next, ParseNodeKind::StringExpr)) {
           return false;
         }
 
         // Stop glomming once folding doesn't produce a string.
         if (!(*next)->isKind(ParseNodeKind::StringExpr)) {
           break;
         }
 
-        // Add this string to the combination and remove the node.
-        tmp = (*next)->as<NameNode>().atom();
-        combination = ConcatStrings<CanGC>(info.cx(), combination, tmp);
-        if (!combination) {
+        // Add this string to the accumulator and remove the node.
+        const ParserAtom* nextAtom = (*next)->as<NameNode>().atom();
+        if (!accum.append(nextAtom)) {
           return false;
         }
 
         (*current)->pn_next = (*next)->pn_next;
         next = &(*current)->pn_next;
 
         node->unsafeDecrementCount();
       } while (*next);
 
-      // Replace |current|'s string with the entire combination.
-      MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
-      combination = AtomizeString(info.cx(), combination);
+      // Construct the concatenated atom.
+      const ParserAtom* combination =
+          info.compilationInfo.parserAtoms
+              .concatAtoms(info.cx(),
+                           mozilla::Range(accum.begin(), accum.length()))
+              .unwrapOr(nullptr);
       if (!combination) {
         return false;
       }
-      (*current)->as<NameNode>().setAtom(&combination->asAtom());
+
+      // Replace |current|'s string with the entire combination.
+      MOZ_ASSERT((*current)->isKind(ParseNodeKind::StringExpr));
+      (*current)->as<NameNode>().setAtom(combination);
 
       // If we're out of nodes, we're done.
       if (!*next) {
         break;
       }
 
       current = next;
       next = &(*current)->pn_next;
--- a/js/src/frontend/Frontend2.cpp
+++ b/js/src/frontend/Frontend2.cpp
@@ -16,123 +16,123 @@
 #include <stdint.h>  // uint8_t, uint32_t
 
 #include "jsapi.h"
 
 #include "frontend/AbstractScopePtr.h"  // ScopeIndex
 #include "frontend/BytecodeSection.h"   // EmitScriptThingsVector
 #include "frontend/CompilationInfo.h"   // CompilationInfo
 #include "frontend/Parser.h"  // NewEmptyLexicalScopeData, NewEmptyGlobalScopeData, NewEmptyVarScopeData, NewEmptyFunctionScopeData
+#include "frontend/ParserAtom.h"        // ParserAtomsTable
 #include "frontend/smoosh_generated.h"  // CVec, Smoosh*, smoosh_*
 #include "frontend/SourceNotes.h"       // SrcNote
 #include "frontend/Stencil.h"  // ScopeStencil, RegExpIndex, FunctionIndex, NullScriptThing
 #include "frontend/TokenStream.h"  // TokenStreamAnyChars
-#include "gc/Rooting.h"            // RootedScriptSourceObject
 #include "irregexp/RegExpAPI.h"    // irregexp::CheckPatternSyntax
 #include "js/CharacterEncoding.h"  // JS::UTF8Chars, UTF8CharsToNewTwoByteCharsZ
 #include "js/GCAPI.h"              // JS::AutoCheckCannotGC
-#include "js/GCVector.h"           // JS::RootedVector
 #include "js/HeapAPI.h"            // JS::GCCellPtr
 #include "js/RegExpFlags.h"        // JS::RegExpFlag, JS::RegExpFlags
-#include "js/RootingAPI.h"         // JS::Handle, JS::Rooted
-#include "js/TypeDecls.h"  // Rooted{Script,Value,String,Object}, JS*:HandleVector, JS::MutableHandleVector
-#include "js/UniquePtr.h"  // js::UniquePtr
-#include "js/Utility.h"    // JS::UniqueTwoByteChars, StringBufferArena
-#include "vm/JSAtom.h"     // AtomizeUTF8Chars
-#include "vm/JSScript.h"   // JSScript
-#include "vm/Scope.h"      // BindingName
-#include "vm/ScopeKind.h"  // ScopeKind
+#include "js/RootingAPI.h"         // JS::MutableHandle
+#include "js/UniquePtr.h"          // js::UniquePtr
+#include "js/Utility.h"            // JS::UniqueTwoByteChars, StringBufferArena
+#include "vm/JSScript.h"           // JSScript
+#include "vm/ScopeKind.h"          // ScopeKind
 #include "vm/SharedStencil.h"  // ImmutableScriptData, ScopeNote, TryNote, GCThingIndex
-#include "vm/StringType.h"  // JSAtom
 
 #include "vm/JSContext-inl.h"  // AutoKeepAtoms (used by BytecodeCompiler)
 
 using mozilla::Utf8Unit;
 
 using namespace js::gc;
 using namespace js::frontend;
 using namespace js;
 
 namespace js {
 
 namespace frontend {
 
 // Given the result of SmooshMonkey's parser, Convert the list of atoms into
-// the list of JSAtoms.
+// the list of ParserAtoms.
 bool ConvertAtoms(JSContext* cx, const SmooshResult& result,
                   CompilationInfo& compilationInfo,
-                  JS::MutableHandleVector<JSAtom*> allAtoms) {
+                  Vector<const ParserAtom*>& allAtoms) {
   size_t numAtoms = result.all_atoms_len;
 
   if (!allAtoms.reserve(numAtoms)) {
     return false;
   }
 
   for (size_t i = 0; i < numAtoms; i++) {
-    auto s = smoosh_get_atom_at(result, i);
+    auto s = reinterpret_cast<const mozilla::Utf8Unit*>(
+        smoosh_get_atom_at(result, i));
     auto len = smoosh_get_atom_len_at(result, i);
-    JSAtom* atom = AtomizeUTF8Chars(cx, s, len);
+    const ParserAtom* atom =
+        compilationInfo.parserAtoms.internUtf8(cx, s, len).unwrapOr(nullptr);
     if (!atom) {
       return false;
     }
     allAtoms.infallibleAppend(atom);
   }
 
   return true;
 }
 
 void CopyBindingNames(JSContext* cx, CVec<SmooshBindingName>& from,
-                      JS::HandleVector<JSAtom*> allAtoms, BindingName* to) {
+                      Vector<const ParserAtom*>& allAtoms,
+                      ParserBindingName* to) {
   // We're setting trailing array's content before setting its length.
   JS::AutoCheckCannotGC nogc(cx);
 
   size_t numBindings = from.len;
   for (size_t i = 0; i < numBindings; i++) {
     SmooshBindingName& name = from.data[i];
-    new (mozilla::KnownNotNull, &to[i]) BindingName(
+    new (mozilla::KnownNotNull, &to[i]) ParserBindingName(
         allAtoms[name.name], name.is_closed_over, name.is_top_level_function);
   }
 }
 
 void CopyBindingNames(JSContext* cx, CVec<COption<SmooshBindingName>>& from,
-                      JS::HandleVector<JSAtom*> allAtoms, BindingName* to) {
+                      Vector<const ParserAtom*>& allAtoms,
+                      ParserBindingName* to) {
   // We're setting trailing array's content before setting its length.
   JS::AutoCheckCannotGC nogc(cx);
 
   size_t numBindings = from.len;
   for (size_t i = 0; i < numBindings; i++) {
     COption<SmooshBindingName>& maybeName = from.data[i];
     if (maybeName.IsSome()) {
       SmooshBindingName& name = maybeName.AsSome();
-      new (mozilla::KnownNotNull, &to[i]) BindingName(
+      new (mozilla::KnownNotNull, &to[i]) ParserBindingName(
           allAtoms[name.name], name.is_closed_over, name.is_top_level_function);
     } else {
-      new (mozilla::KnownNotNull, &to[i]) BindingName(nullptr, false, false);
+      new (mozilla::KnownNotNull, &to[i])
+          ParserBindingName(nullptr, false, false);
     }
   }
 }
 
 // Given the result of SmooshMonkey's parser, convert a list of scope data
 // into a list of ScopeStencil.
 bool ConvertScopeStencil(JSContext* cx, const SmooshResult& result,
-                         JS::HandleVector<JSAtom*> allAtoms,
+                         Vector<const ParserAtom*>& allAtoms,
                          CompilationInfo& compilationInfo) {
   auto& alloc = compilationInfo.allocScope.alloc();
 
   for (size_t i = 0; i < result.scopes.len; i++) {
     SmooshScopeData& scopeData = result.scopes.data[i];
     ScopeIndex index;
 
     switch (scopeData.tag) {
       case SmooshScopeData::Tag::Global: {
         auto& global = scopeData.AsGlobal();
 
         size_t numBindings = global.bindings.len;
-        JS::Rooted<GlobalScope::Data*> data(
-            cx, NewEmptyGlobalScopeData(cx, alloc, numBindings));
+        ParserGlobalScopeData* data =
+            NewEmptyGlobalScopeData(cx, alloc, numBindings);
         if (!data) {
           return false;
         }
 
         CopyBindingNames(cx, global.bindings, allAtoms,
                          data->trailingNames.start());
 
         data->letStart = global.let_start;
@@ -145,18 +145,18 @@ bool ConvertScopeStencil(JSContext* cx, 
         }
         break;
       }
       case SmooshScopeData::Tag::Var: {
         auto& var = scopeData.AsVar();
 
         size_t numBindings = var.bindings.len;
 
-        JS::Rooted<VarScope::Data*> data(
-            cx, NewEmptyVarScopeData(cx, alloc, numBindings));
+        ParserVarScopeData* data = NewEmptyVarScopeData(cx, alloc, numBindings);
+        ;
         if (!data) {
           return false;
         }
 
         CopyBindingNames(cx, var.bindings, allAtoms,
                          data->trailingNames.start());
 
         // NOTE: data->nextFrameSlot is set in ScopeStencil::createForVarScope.
@@ -172,18 +172,18 @@ bool ConvertScopeStencil(JSContext* cx, 
           return false;
         }
         break;
       }
       case SmooshScopeData::Tag::Lexical: {
         auto& lexical = scopeData.AsLexical();
 
         size_t numBindings = lexical.bindings.len;
-        JS::Rooted<LexicalScope::Data*> data(
-            cx, NewEmptyLexicalScopeData(cx, alloc, numBindings));
+        ParserLexicalScopeData* data =
+            NewEmptyLexicalScopeData(cx, alloc, numBindings);
         if (!data) {
           return false;
         }
 
         CopyBindingNames(cx, lexical.bindings, allAtoms,
                          data->trailingNames.start());
 
         // NOTE: data->nextFrameSlot is set in
@@ -200,18 +200,18 @@ bool ConvertScopeStencil(JSContext* cx, 
           return false;
         }
         break;
       }
       case SmooshScopeData::Tag::Function: {
         auto& function = scopeData.AsFunction();
 
         size_t numBindings = function.bindings.len;
-        JS::Rooted<FunctionScope::Data*> data(
-            cx, NewEmptyFunctionScopeData(cx, alloc, numBindings));
+        ParserFunctionScopeData* data =
+            NewEmptyFunctionScopeData(cx, alloc, numBindings);
         if (!data) {
           return false;
         }
 
         CopyBindingNames(cx, function.bindings, allAtoms,
                          data->trailingNames.start());
 
         // NOTE: data->nextFrameSlot is set in
@@ -338,17 +338,17 @@ UniquePtr<ImmutableScriptData> ConvertIm
       mozilla::Span<const SrcNote>(), mozilla::Span<const uint32_t>(),
       scopeNotes, mozilla::Span<const TryNote>());
 }
 
 // Given the result of SmooshMonkey's parser, convert a list of GC things
 // used by a script into ScriptThingsVector.
 bool ConvertGCThings(JSContext* cx, const SmooshResult& result,
                      const SmooshScriptStencil& smooshStencil,
-                     JS::HandleVector<JSAtom*> allAtoms,
+                     Vector<const ParserAtom*>& allAtoms,
                      MutableHandle<ScriptStencil> stencil) {
   auto& gcThings = stencil.get().gcThings;
 
   size_t ngcthings = smooshStencil.gcthings.len;
   if (!gcThings.reserve(ngcthings)) {
     return false;
   }
 
@@ -356,18 +356,17 @@ bool ConvertGCThings(JSContext* cx, cons
     SmooshGCThing& item = smooshStencil.gcthings.data[i];
 
     switch (item.tag) {
       case SmooshGCThing::Tag::Null: {
         gcThings.infallibleAppend(NullScriptThing());
         break;
       }
       case SmooshGCThing::Tag::Atom: {
-        gcThings.infallibleAppend(
-            mozilla::AsVariant(allAtoms[item.AsAtom()].get()));
+        gcThings.infallibleAppend(mozilla::AsVariant(allAtoms[item.AsAtom()]));
         break;
       }
       case SmooshGCThing::Tag::Function: {
         gcThings.infallibleAppend(
             mozilla::AsVariant(FunctionIndex(item.AsFunction())));
         break;
       }
       case SmooshGCThing::Tag::Scope: {
@@ -388,17 +387,17 @@ bool ConvertGCThings(JSContext* cx, cons
 
 // Given the result of SmooshMonkey's parser, convert a specific script
 // or function to a StencilScript, given a fixed set of source atoms.
 //
 // The StencilScript would then be in charge of handling the lifetime and
 // (until GC things gets removed from stencil) tracing API of the GC.
 bool ConvertScriptStencil(JSContext* cx, const SmooshResult& result,
                           const SmooshScriptStencil& smooshStencil,
-                          JS::HandleVector<JSAtom*> allAtoms,
+                          Vector<const ParserAtom*>& allAtoms,
                           CompilationInfo& compilationInfo,
                           MutableHandle<ScriptStencil> stencil) {
   using ImmutableFlags = js::ImmutableScriptFlagsEnum;
 
   const JS::ReadOnlyCompileOptions& options = compilationInfo.options;
 
   stencil.get().immutableFlags = smooshStencil.immutable_flags;
 
@@ -535,18 +534,18 @@ bool Smoosh::compileGlobalScriptToStenci
 
   if (result.unimplemented) {
     *unimplemented = true;
     return false;
   }
 
   *unimplemented = false;
 
-  JS::RootedVector<JSAtom*> allAtoms(cx);
-  if (!ConvertAtoms(cx, result, compilationInfo, &allAtoms)) {
+  Vector<const ParserAtom*> allAtoms(cx);
+  if (!ConvertAtoms(cx, result, compilationInfo, allAtoms)) {
     return false;
   }
 
   if (!ConvertScopeStencil(cx, result, allAtoms, compilationInfo)) {
     return false;
   }
 
   if (!ConvertRegExpData(cx, result, compilationInfo)) {
--- a/js/src/frontend/FullParseHandler.h
+++ b/js/src/frontend/FullParseHandler.h
@@ -46,16 +46,19 @@ class FullParseHandler {
   /*
    * If this is a full parse to construct the bytecode for a function that
    * was previously lazily parsed, we still don't want to full parse the
    * inner functions. These members are used for this functionality:
    *
    * - lazyOuterFunction_ holds the lazyScript for this current parse
    * - lazyInnerFunctionIndex is used as we skip over inner functions
    *   (see skipLazyInnerFunction),
+   *
+   *  TODO-Stencil: We probably need to snapshot the atoms from the
+   *                lazyOuterFunction here.
    */
   const Rooted<BaseScript*> lazyOuterFunction_;
   size_t lazyInnerFunctionIndex;
 
   size_t lazyClosedOverBindingIndex;
 
   const SourceKind sourceKind_;
 
@@ -135,30 +138,32 @@ class FullParseHandler {
 
   // The FullParseHandler may be used to create nodes for text sources (from
   // Parser.h). With previous binary source formats, some common assumptions on
   // offsets are incorrect, e.g. in `a + b`, `a`, `b` and `+` may be stored in
   // any order. We use `sourceKind()` to determine whether we need to check
   // these assumptions.
   SourceKind sourceKind() const { return sourceKind_; }
 
-  NameNodeType newName(PropertyName* name, const TokenPos& pos, JSContext* cx) {
+  NameNodeType newName(const ParserName* name, const TokenPos& pos,
+                       JSContext* cx) {
     return new_<NameNode>(ParseNodeKind::Name, name, pos);
   }
 
   UnaryNodeType newComputedName(Node expr, uint32_t begin, uint32_t end) {
     TokenPos pos(begin, end);
     return new_<UnaryNode>(ParseNodeKind::ComputedName, pos, expr);
   }
 
-  NameNodeType newObjectLiteralPropertyName(JSAtom* atom, const TokenPos& pos) {
+  NameNodeType newObjectLiteralPropertyName(const ParserAtom* atom,
+                                            const TokenPos& pos) {
     return new_<NameNode>(ParseNodeKind::ObjectPropertyName, atom, pos);
   }
 
-  NameNodeType newPrivateName(JSAtom* atom, const TokenPos& pos) {
+  NameNodeType newPrivateName(const ParserAtom* atom, const TokenPos& pos) {
     return new_<NameNode>(ParseNodeKind::PrivateName, atom, pos);
   }
 
   NumericLiteralType newNumber(double value, DecimalPoint decimalPoint,
                                const TokenPos& pos) {
     return new_<NumericLiteral>(value, decimalPoint, pos);
   }
 
@@ -167,21 +172,22 @@ class FullParseHandler {
                               const TokenPos& pos) {
     return new_<BigIntLiteral>(index, compilationInfo, pos);
   }
 
   BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) {
     return new_<BooleanLiteral>(cond, pos);
   }
 
-  NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) {
+  NameNodeType newStringLiteral(const ParserAtom* atom, const TokenPos& pos) {
     return new_<NameNode>(ParseNodeKind::StringExpr, atom, pos);
   }
 
-  NameNodeType newTemplateStringLiteral(JSAtom* atom, const TokenPos& pos) {
+  NameNodeType newTemplateStringLiteral(const ParserAtom* atom,
+                                        const TokenPos& pos) {
     return new_<NameNode>(ParseNodeKind::TemplateStringExpr, atom, pos);
   }
 
   CallSiteNodeType newCallSiteObject(uint32_t begin) {
     CallSiteNode* callSiteObj = new_<CallSiteNode>(begin);
     if (!callSiteObj) {
       return null();
     }
@@ -721,22 +727,22 @@ class FullParseHandler {
     return new_<SwitchStatement>(begin, discriminant, lexicalForCaseList,
                                  hasDefault);
   }
 
   CaseClauseType newCaseOrDefault(uint32_t begin, Node expr, Node body) {
     return new_<CaseClause>(expr, body, begin);
   }
 
-  ContinueStatementType newContinueStatement(PropertyName* label,
+  ContinueStatementType newContinueStatement(const ParserName* label,
                                              const TokenPos& pos) {
     return new_<ContinueStatement>(label, pos);
   }
 
-  BreakStatementType newBreakStatement(PropertyName* label,
+  BreakStatementType newBreakStatement(const ParserName* label,
                                        const TokenPos& pos) {
     return new_<BreakStatement>(label, pos);
   }
 
   UnaryNodeType newReturnStatement(Node expr, const TokenPos& pos) {
     MOZ_ASSERT_IF(expr && sourceKind() == SourceKind::Text,
                   pos.encloses(expr->pn_pos));
     return new_<UnaryNode>(ParseNodeKind::ReturnStmt, pos, expr);
@@ -746,17 +752,17 @@ class FullParseHandler {
     return new_<UnaryNode>(ParseNodeKind::ReturnStmt, expr->pn_pos, expr);
   }
 
   BinaryNodeType newWithStatement(uint32_t begin, Node expr, Node body) {
     return new_<BinaryNode>(ParseNodeKind::WithStmt,
                             TokenPos(begin, body->pn_pos.end), expr, body);
   }
 
-  LabeledStatementType newLabeledStatement(PropertyName* label, Node stmt,
+  LabeledStatementType newLabeledStatement(const ParserName* label, Node stmt,
                                            uint32_t begin) {
     return new_<LabeledStatement>(label, stmt, begin);
   }
 
   UnaryNodeType newThrowStatement(Node expr, const TokenPos& pos) {
     MOZ_ASSERT_IF(sourceKind() == SourceKind::Text, pos.encloses(expr->pn_pos));
     return new_<UnaryNode>(ParseNodeKind::ThrowStmt, pos, expr);
   }
@@ -766,17 +772,17 @@ class FullParseHandler {
                                   Node finallyBlock) {
     return new_<TryNode>(begin, body, catchScope, finallyBlock);
   }
 
   DebuggerStatementType newDebuggerStatement(const TokenPos& pos) {
     return new_<DebuggerStatement>(pos);
   }
 
-  NameNodeType newPropertyName(PropertyName* name, const TokenPos& pos) {
+  NameNodeType newPropertyName(const ParserName* name, const TokenPos& pos) {
     return new_<NameNode>(ParseNodeKind::PropertyNameExpr, name, pos);
   }
 
   PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) {
     return new_<PropertyAccess>(expr, key, expr->pn_pos.begin, key->pn_pos.end);
   }
 
   PropertyByValueType newPropertyByValue(Node lhs, Node index, uint32_t end) {
@@ -858,17 +864,18 @@ class FullParseHandler {
     MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody));
     addList(/* list = */ funNode->body(), /* kid = */ body);
   }
 
   ModuleNodeType newModule(const TokenPos& pos) {
     return new_<ModuleNode>(pos);
   }
 
-  LexicalScopeNodeType newLexicalScope(LexicalScope::Data* bindings, Node body,
+  LexicalScopeNodeType newLexicalScope(ParserLexicalScopeData* bindings,
+                                       Node body,
                                        ScopeKind kind = ScopeKind::Lexical) {
     return new_<LexicalScopeNode>(bindings, body, kind);
   }
 
   CallNodeType newNewExpression(uint32_t begin, Node ctor, Node args,
                                 bool isSpread) {
     return new_<CallNode>(ParseNodeKind::NewExpr,
                           isSpread ? JSOp::SpreadNew : JSOp::New,
@@ -1049,24 +1056,24 @@ class FullParseHandler {
       }
     }
     if (node->isKind(ParseNodeKind::OptionalChain)) {
       return isPrivateField(node->as<UnaryNode>().kid());
     }
     return false;
   }
 
-  PropertyName* maybeDottedProperty(Node pn) {
-    return pn->is<PropertyAccessBase>() ? &pn->as<PropertyAccessBase>().name()
+  const ParserName* maybeDottedProperty(Node pn) {
+    return pn->is<PropertyAccessBase>() ? pn->as<PropertyAccessBase>().name()
                                         : nullptr;
   }
-  JSAtom* isStringExprStatement(Node pn, TokenPos* pos) {
+  const ParserAtom* isStringExprStatement(Node pn, TokenPos* pos) {
     if (pn->is<UnaryNode>()) {
       UnaryNode* unary = &pn->as<UnaryNode>();
-      if (JSAtom* atom = unary->isStringExprStatement()) {
+      if (const ParserAtom* atom = unary->isStringExprStatement()) {
         *pos = unary->kid()->pn_pos;
         return atom;
       }
     }
     return nullptr;
   }
 
   bool canSkipLazyInnerFunctions() { return !!lazyOuterFunction_; }
--- a/js/src/frontend/FunctionEmitter.cpp
+++ b/js/src/frontend/FunctionEmitter.cpp
@@ -30,17 +30,17 @@ using namespace js::frontend;
 using mozilla::Maybe;
 using mozilla::Some;
 
 FunctionEmitter::FunctionEmitter(BytecodeEmitter* bce, FunctionBox* funbox,
                                  FunctionSyntaxKind syntaxKind,
                                  IsHoisted isHoisted)
     : bce_(bce),
       funbox_(funbox),
-      name_(bce_->cx, funbox_->explicitName()),
+      name_(funbox_->explicitName()),
       syntaxKind_(syntaxKind),
       isHoisted_(isHoisted) {}
 
 bool FunctionEmitter::prepareForNonLazy() {
   MOZ_ASSERT(state_ == State::Start);
 
   MOZ_ASSERT(funbox_->isInterpreted());
   MOZ_ASSERT(funbox_->emitBytecode);
@@ -510,18 +510,19 @@ bool FunctionScriptEmitter::emitExtraBod
   }
 
   // After emitting expressions for all parameters, copy over any formal
   // parameters which have been redeclared as vars. For example, in the
   // following, the var y in the body scope is 42:
   //
   //   function f(x, y = 42) { var y; }
   //
-  JS::Rooted<JSAtom*> name(bce_->cx);
-  for (BindingIter bi(*funbox_->functionScopeBindings(), true); bi; bi++) {
+  const ParserAtom* name = nullptr;
+  for (ParserBindingIter bi(*funbox_->functionScopeBindings(), true); bi;
+       bi++) {
     name = bi.name();
 
     // There may not be a var binding of the same name.
     if (!bce_->locationOfNameBoundInScope(name,
                                           extraBodyVarEmitterScope_.ptr())) {
       continue;
     }
 
@@ -693,34 +694,34 @@ bool FunctionScriptEmitter::emitEndBody(
   state_ = State::EndBody;
 #endif
   return true;
 }
 
 bool FunctionScriptEmitter::intoStencil() {
   MOZ_ASSERT(state_ == State::EndBody);
 
-  if (!bce_->intoScriptStencil(funbox_->functionStencil().address())) {
+  if (!bce_->intoScriptStencil(&funbox_->functionStencil().get())) {
     return false;
   }
 
 #ifdef DEBUG
   state_ = State::End;
 #endif
 
   return true;
 }
 
 FunctionParamsEmitter::FunctionParamsEmitter(BytecodeEmitter* bce,
                                              FunctionBox* funbox)
     : bce_(bce),
       funbox_(funbox),
       functionEmitterScope_(bce_->innermostEmitterScope()) {}
 
-bool FunctionParamsEmitter::emitSimple(JS::Handle<JSAtom*> paramName) {
+bool FunctionParamsEmitter::emitSimple(const ParserAtom* paramName) {
   MOZ_ASSERT(state_ == State::Start);
 
   //                [stack]
 
   if (funbox_->hasParameterExprs) {
     if (!bce_->emitArgOp(JSOp::GetArg, argSlot_)) {
       //            [stack] ARG
       return false;
@@ -747,17 +748,17 @@ bool FunctionParamsEmitter::prepareForDe
   }
 
 #ifdef DEBUG
   state_ = State::Default;
 #endif
   return true;
 }
 
-bool FunctionParamsEmitter::emitDefaultEnd(JS::Handle<JSAtom*> paramName) {
+bool FunctionParamsEmitter::emitDefaultEnd(const ParserAtom* paramName) {
   MOZ_ASSERT(state_ == State::Default);
 
   //                [stack] DEFAULT
 
   if (!emitInitializerEnd()) {
     //              [stack] ARG/DEFAULT
     return false;
   }
@@ -853,17 +854,17 @@ bool FunctionParamsEmitter::emitDestruct
   argSlot_++;
 
 #ifdef DEBUG
   state_ = State::Start;
 #endif
   return true;
 }
 
-bool FunctionParamsEmitter::emitRest(JS::Handle<JSAtom*> paramName) {
+bool FunctionParamsEmitter::emitRest(const ParserAtom* paramName) {
   MOZ_ASSERT(state_ == State::Start);
 
   //                [stack]
 
   if (!emitRestArray()) {
     //              [stack] REST
     return false;
   }
@@ -944,17 +945,17 @@ bool FunctionParamsEmitter::emitRestArra
 
   if (!bce_->emit1(JSOp::Rest)) {
     //              [stack] REST
     return false;
   }
   return true;
 }
 
-bool FunctionParamsEmitter::emitAssignment(JS::Handle<JSAtom*> paramName) {
+bool FunctionParamsEmitter::emitAssignment(const ParserAtom* paramName) {
   //                [stack] ARG
 
   NameLocation paramLoc =
       *bce_->locationOfNameBoundInScope(paramName, functionEmitterScope_);
 
   // RHS is already pushed in the caller side.
   // Make sure prepareForRhs doesn't touch stack.
   MOZ_ASSERT(paramLoc.kind() == NameLocation::Kind::ArgumentSlot ||
--- a/js/src/frontend/FunctionEmitter.h
+++ b/js/src/frontend/FunctionEmitter.h
@@ -67,17 +67,17 @@ class MOZ_STACK_CLASS FunctionEmitter {
   enum class IsHoisted { No, Yes };
 
  private:
   BytecodeEmitter* bce_;
 
   FunctionBox* funbox_;
 
   // Function's explicit name.
-  JS::Rooted<JSAtom*> name_;
+  const ParserAtom* name_;
 
   FunctionSyntaxKind syntaxKind_;
   IsHoisted isHoisted_;
 
 #ifdef DEBUG
   // The state of this emitter.
   //
   // +-------+
@@ -405,38 +405,38 @@ class MOZ_STACK_CLASS FunctionParamsEmit
   State state_ = State::Start;
 #endif
 
  public:
   FunctionParamsEmitter(BytecodeEmitter* bce, FunctionBox* funbox);
 
   // paramName is used only when there's at least one expression in the
   // paramerters (funbox_->hasParameterExprs == true).
-  MOZ_MUST_USE bool emitSimple(JS::Handle<JSAtom*> paramName);
+  MOZ_MUST_USE bool emitSimple(const ParserAtom* paramName);
 
   MOZ_MUST_USE bool prepareForDefault();
-  MOZ_MUST_USE bool emitDefaultEnd(JS::Handle<JSAtom*> paramName);
+  MOZ_MUST_USE bool emitDefaultEnd(const ParserAtom* paramName);
 
   MOZ_MUST_USE bool prepareForDestructuring();
   MOZ_MUST_USE bool emitDestructuringEnd();
 
   MOZ_MUST_USE bool prepareForDestructuringDefaultInitializer();
   MOZ_MUST_USE bool prepareForDestructuringDefault();
   MOZ_MUST_USE bool emitDestructuringDefaultEnd();
 
-  MOZ_MUST_USE bool emitRest(JS::Handle<JSAtom*> paramName);
+  MOZ_MUST_USE bool emitRest(const ParserAtom* paramName);
 
   MOZ_MUST_USE bool prepareForDestructuringRest();
   MOZ_MUST_USE bool emitDestructuringRestEnd();
 
  private:
   MOZ_MUST_USE bool prepareForInitializer();
   MOZ_MUST_USE bool emitInitializerEnd();
 
   MOZ_MUST_USE bool emitRestArray();
 
-  MOZ_MUST_USE bool emitAssignment(JS::Handle<JSAtom*> paramName);
+  MOZ_MUST_USE bool emitAssignment(const ParserAtom* paramName);
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_FunctionEmitter_h */
--- a/js/src/frontend/LabelEmitter.cpp
+++ b/js/src/frontend/LabelEmitter.cpp
@@ -8,17 +8,17 @@
 
 #include "mozilla/Assertions.h"  // MOZ_ASSERT
 
 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
 
 using namespace js;
 using namespace js::frontend;
 
-void LabelEmitter::emitLabel(HandleAtom name) {
+void LabelEmitter::emitLabel(const ParserAtom* name) {
   MOZ_ASSERT(state_ == State::Start);
 
   controlInfo_.emplace(bce_, name, bce_->bytecodeSection().offset());
 
 #ifdef DEBUG
   state_ = State::Label;
 #endif
 }
--- a/js/src/frontend/LabelEmitter.h
+++ b/js/src/frontend/LabelEmitter.h
@@ -52,16 +52,16 @@ class MOZ_STACK_CLASS LabelEmitter {
     End
   };
   State state_ = State::Start;
 #endif
 
  public:
   explicit LabelEmitter(BytecodeEmitter* bce) : bce_(bce) {}
 
-  void emitLabel(HandleAtom name);
+  void emitLabel(const ParserAtom* name);
   MOZ_MUST_USE bool emitEnd();
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_LabelEmitter_h */
--- a/js/src/frontend/LexicalScopeEmitter.cpp
+++ b/js/src/frontend/LexicalScopeEmitter.cpp
@@ -9,17 +9,17 @@
 #include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
 
 using namespace js;
 using namespace js::frontend;
 
 LexicalScopeEmitter::LexicalScopeEmitter(BytecodeEmitter* bce) : bce_(bce) {}
 
 bool LexicalScopeEmitter::emitScope(ScopeKind kind,
-                                    JS::Handle<LexicalScope::Data*> bindings) {
+                                    ParserLexicalScopeData* bindings) {
   MOZ_ASSERT(state_ == State::Start);
   MOZ_ASSERT(bindings);
 
   tdzCache_.emplace(bce_);
   emitterScope_.emplace(bce_);
   if (!emitterScope_->enterLexical(bce_, kind, bindings)) {
     return false;
   }
--- a/js/src/frontend/LexicalScopeEmitter.h
+++ b/js/src/frontend/LexicalScopeEmitter.h
@@ -78,18 +78,17 @@ class MOZ_STACK_CLASS LexicalScopeEmitte
 #endif
 
  public:
   explicit LexicalScopeEmitter(BytecodeEmitter* bce);
 
   // Returns the scope object for non-empty scope.
   const EmitterScope& emitterScope() const { return *emitterScope_; }
 
-  MOZ_MUST_USE bool emitScope(ScopeKind kind,
-                              JS::Handle<LexicalScope::Data*> bindings);
+  MOZ_MUST_USE bool emitScope(ScopeKind kind, ParserLexicalScopeData* bindings);
   MOZ_MUST_USE bool emitEmptyScope();
 
   MOZ_MUST_USE bool emitEnd();
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
--- a/js/src/frontend/ModuleSharedContext.h
+++ b/js/src/frontend/ModuleSharedContext.h
@@ -18,17 +18,17 @@
 namespace js {
 
 class ModuleBuilder;
 
 namespace frontend {
 
 class MOZ_STACK_CLASS ModuleSharedContext : public SharedContext {
  public:
-  JS::Rooted<ModuleScope::Data*> bindings;
+  ParserModuleScopeData* bindings;
   ModuleBuilder& builder;
 
   ModuleSharedContext(JSContext* cx, CompilationInfo& compilationInfo,
                       ModuleBuilder& builder, SourceExtent extent);
 };
 
 inline ModuleSharedContext* SharedContext::asModuleContext() {
   MOZ_ASSERT(isModuleContext());
--- a/js/src/frontend/NameAnalysisTypes.h
+++ b/js/src/frontend/NameAnalysisTypes.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef frontend_NameAnalysisTypes_h
 #define frontend_NameAnalysisTypes_h
 
 #include <type_traits>
 
+#include "frontend/ParserAtom.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Scope.h"
 
 namespace js {
 
 // An "environment coordinate" describes how to get from head of the
 // environment chain to a given lexically-enclosing variable. An environment
 // coordinate has two dimensions:
@@ -348,17 +349,17 @@ class NameLocation {
 
   bool hasKnownSlot() const {
     return kind_ == Kind::ArgumentSlot || kind_ == Kind::FrameSlot ||
            kind_ == Kind::EnvironmentCoordinate;
   }
 };
 
 // These types are declared here for BaseScript::CreateLazy.
-using AtomVector = Vector<JSAtom*, 24, SystemAllocPolicy>;
+using AtomVector = Vector<const ParserAtom*, 24, SystemAllocPolicy>;
 
 class FunctionBox;
 // FunctionBoxes stored in this type are required to be rooted
 // by the parser
 using FunctionBoxVector = Vector<const FunctionBox*, 8>;
 
 }  // namespace frontend
 }  // namespace js
--- a/js/src/frontend/NameCollections.h
+++ b/js/src/frontend/NameCollections.h
@@ -129,30 +129,30 @@ struct RecyclableAtomMapValueWrapper {
 
   MOZ_IMPLICIT operator Wrapped&() const { return wrapped; }
 
   Wrapped* operator->() { return &wrapped; }
 
   const Wrapped* operator->() const { return &wrapped; }
 };
 
-struct NameMapHasher : public DefaultHasher<JSAtom*> {
+struct NameMapHasher : public DefaultHasher<const ParserAtom*> {
   static inline HashNumber hash(const Lookup& l) {
     // Name maps use the atom's precomputed hash code, which is based on
     // the atom's contents rather than its pointer value. This is necessary
     // to preserve iteration order while recording/replaying: iteration can
     // affect generated script bytecode and the order in which e.g. lookup
     // property hooks are performed on the associated global.
     return l->hash();
   }
 };
 
 template <typename MapValue>
 using RecyclableNameMap =
-    InlineMap<JSAtom*, RecyclableAtomMapValueWrapper<MapValue>, 24,
+    InlineMap<const ParserAtom*, RecyclableAtomMapValueWrapper<MapValue>, 24,
               NameMapHasher, SystemAllocPolicy>;
 
 using DeclaredNameMap = RecyclableNameMap<DeclaredNameInfo>;
 using NameLocationMap = RecyclableNameMap<NameLocation>;
 // Cannot use GCThingIndex here because it's not trivial type.
 using AtomIndexMap = RecyclableNameMap<uint32_t>;
 
 template <typename RepresentativeTable>
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -6,16 +6,17 @@
 
 #include "frontend/NameFunctions.h"
 
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Sprintf.h"
 
 #include "frontend/BytecodeCompiler.h"
+#include "frontend/CompilationInfo.h"
 #include "frontend/ParseNode.h"
 #include "frontend/ParseNodeVisitor.h"
 #include "frontend/SharedContext.h"
 #include "util/Poison.h"
 #include "util/StringBuffer.h"
 #include "vm/JSFunction.h"
 
 using namespace js;
@@ -23,17 +24,18 @@ using namespace js::frontend;
 
 namespace {
 
 class NameResolver : public ParseNodeVisitor<NameResolver> {
   using Base = ParseNodeVisitor;
 
   static const size_t MaxParents = 100;
 
-  RootedAtom prefix_;
+  CompilationInfo& compilationInfo_;
+  const ParserAtom* prefix_;
 
   // Number of nodes in the parents array.
   size_t nparents_;
 
   // Stack of ParseNodes from the root to the current node.
   // Only elements 0..nparents_ are initialized.
   MOZ_INIT_OUTSIDE_CTOR
   ParseNode* parents_[MaxParents];
@@ -52,17 +54,17 @@ class NameResolver : public ParseNodeVis
    * a proper identifier name, then we append '.name'; otherwise, we
    * append '["name"]'.
    *
    * Note that we need the IsIdentifier check for atoms from both
    * ParseNodeKind::Name nodes and ParseNodeKind::String nodes:
    * given code like a["b c"], the front end will produce a ParseNodeKind::Dot
    * with a ParseNodeKind::Name child whose name contains spaces.
    */
-  bool appendPropertyReference(JSAtom* name) {
+  bool appendPropertyReference(const ParserAtom* name) {
     if (IsIdentifier(name)) {
       return buf_.append('.') && buf_.append(name);
     }
 
     /* Quote the string as needed. */
     UniqueChars source = QuoteString(cx_, name, '"');
     return source && buf_.append('[') &&
            buf_.append(source.get(), strlen(source.get())) && buf_.append(']');
@@ -213,41 +215,42 @@ class NameResolver : public ParseNodeVis
   }
 
   /*
    * Resolve the name of a function. If the function already has a name
    * listed, then it is skipped. Otherwise an intelligent name is guessed to
    * assign to the function's displayAtom field.
    */
   MOZ_MUST_USE bool resolveFun(FunctionNode* funNode,
-                               MutableHandleAtom retAtom) {
+                               const ParserAtom** retId) {
     MOZ_ASSERT(funNode != nullptr);
+
     FunctionBox* funbox = funNode->funbox();
 
     MOZ_ASSERT(buf_.empty());
     auto resetBuf = mozilla::MakeScopeExit([&] { buf_.clear(); });
 
-    retAtom.set(nullptr);
+    *retId = nullptr;
 
     // If the function already has a name, use that.
-    if (funbox->displayAtom() != nullptr) {
-      if (prefix_ == nullptr) {
-        retAtom.set(funbox->displayAtom());
+    if (funbox->displayAtom()) {
+      if (!prefix_) {
+        *retId = funbox->displayAtom();
         return true;
       }
       if (!buf_.append(prefix_) || !buf_.append('/') ||
           !buf_.append(funbox->displayAtom())) {
         return false;
       }
-      retAtom.set(buf_.finishAtom());
-      return !!retAtom;
+      *retId = buf_.finishParserAtom(compilationInfo_);
+      return !!*retId;
     }
 
     // If a prefix is specified, then it is a form of namespace.
-    if (prefix_ != nullptr) {
+    if (prefix_) {
       if (!buf_.append(prefix_) || !buf_.append('/')) {
         return false;
       }
     }
 
     // Gather all nodes relevant to naming.
     ParseNode* toName[MaxParents];
     size_t size;
@@ -307,43 +310,43 @@ class NameResolver : public ParseNodeVis
         !buf_.append('<')) {
       return false;
     }
 
     if (buf_.empty()) {
       return true;
     }
 
-    retAtom.set(buf_.finishAtom());
-    if (!retAtom) {
+    *retId = buf_.finishParserAtom(compilationInfo_);
+    if (!*retId) {
       return false;
     }
 
     // Skip assigning the guessed name if the function has a (dynamically)
     // computed inferred name.
     if (!funNode->isDirectRHSAnonFunction()) {
-      funbox->setGuessedAtom(retAtom);
+      funbox->setGuessedAtom(*retId);
     }
     return true;
   }
 
   /*
    * Tests whether parents_[pos] is a function call whose callee is cur.
    * This is the case for functions which do things like simply create a scope
    * for new variables and then return an anonymous function using this scope.
    */
   bool isDirectCall(int pos, ParseNode* cur) {
     return pos >= 0 && isCall(parents_[pos]) &&
            parents_[pos]->as<BinaryNode>().left() == cur;
   }
 
  public:
   MOZ_MUST_USE bool visitFunction(FunctionNode* pn) {
-    RootedAtom savedPrefix(cx_, prefix_);
-    RootedAtom newPrefix(cx_);
+    const ParserAtom* savedPrefix = prefix_;
+    const ParserAtom* newPrefix = nullptr;
     if (!resolveFun(pn, &newPrefix)) {
       return false;
     }
 
     // If a function looks like (function(){})() where the parent node
     // of the definition of the function is a call, then it shouldn't
     // contribute anything to the namespace, so don't bother updating
     // the prefix to whatever was returned.
@@ -432,18 +435,22 @@ class NameResolver : public ParseNodeVis
   MOZ_MUST_USE bool visitImportSpecList(ListNode* pn) {
     return internalVisitSpecList(pn);
   }
 
   MOZ_MUST_USE bool visitExportSpecList(ListNode* pn) {
     return internalVisitSpecList(pn);
   }
 
-  explicit NameResolver(JSContext* cx)
-      : ParseNodeVisitor(cx), prefix_(cx), nparents_(0), buf_(cx) {}
+  explicit NameResolver(JSContext* cx, CompilationInfo& compilationInfo)
+      : ParseNodeVisitor(cx),
+        compilationInfo_(compilationInfo),
+        prefix_(nullptr),
+        nparents_(0),
+        buf_(cx) {}
 
   /*
    * Resolve names for all anonymous functions in the given ParseNode tree.
    */
   MOZ_MUST_USE bool visit(ParseNode* pn) {
     // Push pn to the parse node stack.
     if (nparents_ >= MaxParents) {
       // Silently skip very deeply nested functions.
@@ -464,14 +471,15 @@ class NameResolver : public ParseNodeVis
                  sizeof(parents_[initialParents]), MemCheckKind::MakeUndefined);
 
     return ok;
   }
 };
 
 } /* anonymous namespace */
 
-bool frontend::NameFunctions(JSContext* cx, ParseNode* pn) {
+bool frontend::NameFunctions(JSContext* cx, CompilationInfo& compilationInfo,
+                             ParseNode* pn) {
   AutoTraceLog traceLog(TraceLoggerForCurrentThread(cx),
                         TraceLogger_BytecodeNameFunctions);
-  NameResolver nr(cx);
+  NameResolver nr(cx, compilationInfo);
   return nr.visit(pn);
 }
--- a/js/src/frontend/NameFunctions.h
+++ b/js/src/frontend/NameFunctions.h
@@ -10,15 +10,17 @@
 #include "mozilla/Attributes.h"
 
 #include "js/TypeDecls.h"
 
 namespace js {
 namespace frontend {
 
 class ParseNode;
+struct CompilationInfo;
 
-MOZ_MUST_USE bool NameFunctions(JSContext* cx, ParseNode* pn);
+MOZ_MUST_USE bool NameFunctions(JSContext* cx, CompilationInfo& compilationInfo,
+                                ParseNode* pn);
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_NameFunctions_h */
--- a/js/src/frontend/NameOpEmitter.cpp
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -12,21 +12,21 @@
 #include "frontend/TDZCheckCache.h"
 #include "vm/Opcodes.h"
 #include "vm/Scope.h"
 #include "vm/StringType.h"
 
 using namespace js;
 using namespace js::frontend;
 
-NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, Handle<JSAtom*> name,
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* name,
                              Kind kind)
     : bce_(bce), kind_(kind), name_(name), loc_(bce_->lookupName(name_)) {}
 
-NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, Handle<JSAtom*> name,
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* name,
                              const NameLocation& loc, Kind kind)
     : bce_(bce), kind_(kind), name_(name), loc_(loc) {}
 
 bool NameOpEmitter::emitGet() {
   MOZ_ASSERT(state_ == State::Start);
 
   switch (loc_.kind()) {
     case NameLocation::Kind::Dynamic:
--- a/js/src/frontend/NameOpEmitter.h
+++ b/js/src/frontend/NameOpEmitter.h
@@ -75,17 +75,17 @@ class MOZ_STACK_CLASS NameOpEmitter {
 
  private:
   BytecodeEmitter* bce_;
 
   Kind kind_;
 
   bool emittedBindOp_ = false;
 
-  Handle<JSAtom*> name_;
+  const ParserAtom* name_;
 
   GCThingIndex atomIndex_;
 
   NameLocation loc_;
 
 #ifdef DEBUG
   // The state of this emitter.
   //
@@ -128,18 +128,18 @@ class MOZ_STACK_CLASS NameOpEmitter {
 
     // After calling emitAssignment.
     Assignment,
   };
   State state_ = State::Start;
 #endif
 
  public:
-  NameOpEmitter(BytecodeEmitter* bce, Handle<JSAtom*> name, Kind kind);
-  NameOpEmitter(BytecodeEmitter* bce, Handle<JSAtom*> name,
+  NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* name, Kind kind);
+  NameOpEmitter(BytecodeEmitter* bce, const ParserAtom* name,
                 const NameLocation& loc, Kind kind);
 
  private:
   MOZ_MUST_USE bool isCall() const { return kind_ == Kind::Call; }
 
   MOZ_MUST_USE bool isSimpleAssignment() const {
     return kind_ == Kind::SimpleAssignment;
   }
--- a/js/src/frontend/ObjLiteral.cpp
+++ b/js/src/frontend/ObjLiteral.cpp
@@ -1,54 +1,75 @@
 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: set ts=8 sw=2 et tw=0 ft=c:
  *
  * 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/ObjLiteral.h"
+
 #include "mozilla/DebugOnly.h"
+
+#include "frontend/CompilationInfo.h"  // frontend::CompilationInfo
+#include "frontend/ParserAtom.h"  // frontend::ParserAtom, frontend::ParserAtomTable
 #include "js/RootingAPI.h"
 #include "vm/JSAtom.h"
 #include "vm/JSObject.h"
 #include "vm/JSONPrinter.h"  // js::JSONPrinter
 #include "vm/ObjectGroup.h"
 #include "vm/Printer.h"  // js::Fprinter
 
 #include "gc/ObjectKind-inl.h"
 #include "vm/JSAtom-inl.h"
 #include "vm/JSObject-inl.h"
 
 namespace js {
 
-static JS::Value InterpretObjLiteralValue(const ObjLiteralAtomVector& atoms,
-                                          const ObjLiteralInsn& insn) {
+static bool InterpretObjLiteralValue(const ObjLiteralAtomVector& atoms,
+                                     frontend::CompilationInfo& compilationInfo,
+                                     const ObjLiteralInsn& insn,
+                                     JS::Value* valOut) {
   switch (insn.getOp()) {
     case ObjLiteralOpcode::ConstValue:
-      return insn.getConstValue();
+      *valOut = insn.getConstValue();
+      return true;
     case ObjLiteralOpcode::ConstAtom: {
       uint32_t index = insn.getAtomIndex();
-      return StringValue(atoms[index]);
+      // TODO-Stencil
+      //   This needs to be coalesced to wherever jsatom creation is eventually
+      //   Seems like InterpretLiteralObj would be called from main-thread
+      //   stencil instantiation.
+      JSAtom* jsatom = compilationInfo.liftParserAtomToJSAtom(atoms[index]);
+      if (!jsatom) {
+        return false;
+      }
+      *valOut = StringValue(jsatom);
+      return true;
     }
     case ObjLiteralOpcode::Null:
-      return NullValue();
+      *valOut = NullValue();
+      return true;
     case ObjLiteralOpcode::Undefined:
-      return UndefinedValue();
+      *valOut = UndefinedValue();
+      return true;
     case ObjLiteralOpcode::True:
-      return BooleanValue(true);
+      *valOut = BooleanValue(true);
+      return true;
     case ObjLiteralOpcode::False:
-      return BooleanValue(false);
+      *valOut = BooleanValue(false);
+      return true;
     default:
       MOZ_CRASH("Unexpected object-literal instruction opcode");
   }
 }
 
 static JSObject* InterpretObjLiteralObj(
-    JSContext* cx, const ObjLiteralAtomVector& atoms,
+    JSContext* cx, frontend::CompilationInfo& compilationInfo,
+    const ObjLiteralAtomVector& atoms,
     const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
   bool specificGroup = flags.contains(ObjLiteralFlag::SpecificGroup);
   bool singleton = flags.contains(ObjLiteralFlag::Singleton);
   bool noValues = flags.contains(ObjLiteralFlag::NoValues);
 
   ObjLiteralReader reader(literalInsns);
   ObjLiteralInsn insn;
 
@@ -57,22 +78,33 @@ static JSObject* InterpretObjLiteralObj(
   // Compute property values and build the key/value-pair list.
   while (reader.readInsn(&insn)) {
     MOZ_ASSERT(insn.isValid());
 
     jsid propId;
     if (insn.getKey().isArrayIndex()) {
       propId = INT_TO_JSID(insn.getKey().getArrayIndex());
     } else {
-      propId = AtomToId(atoms[insn.getKey().getAtomIndex()]);
+      // TODO-Stencil
+      //   Just a note, but it seems like this is an OK place to convert atoms
+      //   since the other GC allocations in the function (properties vector,
+      //   etc.) would need to be addressed.
+      const frontend::ParserAtom* atom = atoms[insn.getKey().getAtomIndex()];
+      JSAtom* jsatom = compilationInfo.liftParserAtomToJSAtom(atom);
+      if (!jsatom) {
+        return nullptr;
+      }
+      propId = AtomToId(compilationInfo.liftParserAtomToJSAtom(atom));
     }
 
     JS::Value propVal;
     if (!noValues) {
-      propVal = InterpretObjLiteralValue(atoms, insn);
+      if (!InterpretObjLiteralValue(atoms, compilationInfo, insn, &propVal)) {
+        return nullptr;
+      }
     }
 
     if (!properties.emplaceBack(propId, propVal)) {
       return nullptr;
     }
   }
 
   if (specificGroup) {
@@ -81,28 +113,32 @@ static JSObject* InterpretObjLiteralObj(
         singleton ? SingletonObject : TenuredObject);
   }
 
   return NewPlainObjectWithProperties(cx, properties.begin(),
                                       properties.length(), TenuredObject);
 }
 
 static JSObject* InterpretObjLiteralArray(
-    JSContext* cx, const ObjLiteralAtomVector& atoms,
+    JSContext* cx, frontend::CompilationInfo& compilationInfo,
+    const ObjLiteralAtomVector& atoms,
     const mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
   bool isCow = flags.contains(ObjLiteralFlag::ArrayCOW);
   ObjLiteralReader reader(literalInsns);
   ObjLiteralInsn insn;
 
   Rooted<ValueVector> elements(cx, ValueVector(cx));
 
   while (reader.readInsn(&insn)) {
     MOZ_ASSERT(insn.isValid());
 
-    JS::Value propVal = InterpretObjLiteralValue(atoms, insn);
+    JS::Value propVal;
+    if (!InterpretObjLiteralValue(atoms, compilationInfo, insn, &propVal)) {
+      return nullptr;
+    }
     if (!elements.append(propVal)) {
       return nullptr;
     }
   }
 
   ObjectGroup::NewArrayKind arrayKind =
       isCow ? ObjectGroup::NewArrayKind::CopyOnWrite
             : ObjectGroup::NewArrayKind::Normal;
@@ -111,22 +147,26 @@ static JSObject* InterpretObjLiteralArra
                                       NewObjectKind::TenuredObject, arrayKind));
   if (!result) {
     return nullptr;
   }
 
   return result;
 }
 
-JSObject* InterpretObjLiteral(JSContext* cx, const ObjLiteralAtomVector& atoms,
+JSObject* InterpretObjLiteral(JSContext* cx,
+                              frontend::CompilationInfo& compilationInfo,
+                              const ObjLiteralAtomVector& atoms,
                               const mozilla::Span<const uint8_t> literalInsns,
                               ObjLiteralFlags flags) {
   return flags.contains(ObjLiteralFlag::Array)
-             ? InterpretObjLiteralArray(cx, atoms, literalInsns, flags)
-             : InterpretObjLiteralObj(cx, atoms, literalInsns, flags);
+             ? InterpretObjLiteralArray(cx, compilationInfo, atoms,
+                                        literalInsns, flags)
+             : InterpretObjLiteralObj(cx, compilationInfo, atoms, literalInsns,
+                                      flags);
 }
 
 #if defined(DEBUG) || defined(JS_JITSPEW)
 
 static void DumpObjLiteralFlagsItems(js::JSONPrinter& json,
                                      ObjLiteralFlags flags) {
   if (flags.contains(ObjLiteralFlag::Array)) {
     json.value("Array");
--- a/js/src/frontend/ObjLiteral.h
+++ b/js/src/frontend/ObjLiteral.h
@@ -7,16 +7,17 @@
 
 #ifndef frontend_ObjLiteral_h
 #define frontend_ObjLiteral_h
 
 #include "mozilla/EndianUtils.h"
 #include "mozilla/EnumSet.h"
 #include "mozilla/Span.h"
 
+#include "frontend/ParserAtom.h"
 #include "js/AllocPolicy.h"
 #include "js/GCPolicyAPI.h"
 #include "js/Value.h"
 #include "js/Vector.h"
 
 /*
  * [SMDOC] ObjLiteral (Object Literal) Handling
  * ============================================
@@ -136,16 +137,20 @@
  * that is in singleton (run-once) context but also `isInner`, then we set
  * special flags to ensure its shape is looked up based on properties instead.
  */
 
 namespace js {
 
 class JSONPrinter;
 
+namespace frontend {
+struct CompilationInfo;
+}
+
 // Object-literal instruction opcodes. An object literal is constructed by a
 // straight-line sequence of these ops, each adding one property to the
 // object.
 enum class ObjLiteralOpcode : uint8_t {
   INVALID = 0,
 
   ConstValue = 1,  // numeric types only.
   ConstAtom = 2,
@@ -539,44 +544,48 @@ struct ObjLiteralReader : private ObjLit
       *insn = ObjLiteralInsn(op, key, atomIndex);
       return true;
     }
     *insn = ObjLiteralInsn(op, key);
     return true;
   }
 };
 
-typedef Vector<JSAtom*, 4> ObjLiteralAtomVector;
+typedef Vector<const frontend::ParserAtom*, 4> ObjLiteralAtomVector;
 
-JSObject* InterpretObjLiteral(JSContext* cx, const ObjLiteralAtomVector& atoms,
+JSObject* InterpretObjLiteral(JSContext* cx,
+                              frontend::CompilationInfo& compilationInfo,
+                              const ObjLiteralAtomVector& atoms,
                               const mozilla::Span<const uint8_t> insns,
                               ObjLiteralFlags flags);
 
 inline JSObject* InterpretObjLiteral(JSContext* cx,
+                                     frontend::CompilationInfo& compilationInfo,
                                      const ObjLiteralAtomVector& atoms,
                                      const ObjLiteralWriter& writer) {
-  return InterpretObjLiteral(cx, atoms, writer.getCode(), writer.getFlags());
+  return InterpretObjLiteral(cx, compilationInfo, atoms, writer.getCode(),
+                             writer.getFlags());
 }
 
 class ObjLiteralStencil {
  private:
   ObjLiteralWriter writer_;
   ObjLiteralAtomVector atoms_;
 
  public:
   explicit ObjLiteralStencil(JSContext* cx) : writer_(cx), atoms_(cx) {}
 
   ObjLiteralWriter& writer() { return writer_; }
 
-  bool addAtom(JSAtom* atom, uint32_t* index) {
+  bool addAtom(const frontend::ParserAtom* atom, uint32_t* index) {
     *index = atoms_.length();
     return atoms_.append(atom);
   }
 
-  JSObject* create(JSContext* cx) const;
+  JSObject* create(JSContext* cx, frontend::CompilationInfo& info) const;
 
 #if defined(DEBUG) || defined(JS_JITSPEW)
   void dump();
   void dump(JSONPrinter& json);
   void dumpFields(JSONPrinter& json);
 #endif
 };
 
--- a/js/src/frontend/ObjectEmitter.cpp
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -16,17 +16,17 @@
 #include "js/Id.h"                     // jsid
 #include "js/Value.h"                  // UndefinedHandleValue
 #include "vm/BytecodeUtil.h"           // IsHiddenInitOp
 #include "vm/FunctionPrefixKind.h"     // FunctionPrefixKind
 #include "vm/JSContext.h"              // JSContext
 #include "vm/NativeObject.h"           // NativeDefineDataProperty
 #include "vm/ObjectGroup.h"            // TenuredObject
 #include "vm/Opcodes.h"                // JSOp
-#include "vm/Runtime.h"                // JSAtomState (cx->parserNames())
+#include "vm/Runtime.h"                // cx->parserNames()
 #include "vm/SharedStencil.h"          // GCThingIndex
 
 #include "gc/ObjectKind-inl.h"  // GetGCObjectKind
 #include "vm/JSAtom-inl.h"      // AtomToId
 #include "vm/JSObject-inl.h"    // NewBuiltinClassInstance
 
 using namespace js;
 using namespace js::frontend;
@@ -258,17 +258,18 @@ bool PropertyEmitter::emitInitHomeObject
     propertyState_ = PropertyState::InitHomeObjForIndex;
   } else {
     propertyState_ = PropertyState::InitHomeObjForComputed;
   }
 #endif
   return true;
 }
 
-bool PropertyEmitter::emitInit(AccessorType accessorType, HandleAtom key) {
+bool PropertyEmitter::emitInit(AccessorType accessorType,
+                               const ParserAtom* key) {
   switch (accessorType) {
     case AccessorType::None:
       return emitInit(isClass_ ? JSOp::InitHiddenProp : JSOp::InitProp, key);
     case AccessorType::Getter:
       return emitInit(
           isClass_ ? JSOp::InitHiddenPropGetter : JSOp::InitPropGetter, key);
     case AccessorType::Setter:
       return emitInit(
@@ -289,17 +290,17 @@ bool PropertyEmitter::emitInitIndexOrCom
     case AccessorType::Setter:
       return emitInitIndexOrComputed(isClass_ ? JSOp::InitHiddenElemSetter
                                               : JSOp::InitElemSetter);
     default:
       MOZ_CRASH("Invalid op");
   }
 }
 
-bool PropertyEmitter::emitInit(JSOp op, JS::Handle<JSAtom*> key) {
+bool PropertyEmitter::emitInit(JSOp op, const ParserAtom* key) {
   MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
              propertyState_ == PropertyState::InitHomeObj);
 
   MOZ_ASSERT(op == JSOp::InitProp || op == JSOp::InitHiddenProp ||
              op == JSOp::InitPropGetter || op == JSOp::InitHiddenPropGetter ||
              op == JSOp::InitPropSetter || op == JSOp::InitHiddenPropSetter);
 
   //                [stack] CTOR? OBJ CTOR? VAL
@@ -417,22 +418,22 @@ AutoSaveLocalStrictMode::~AutoSaveLocalS
 void AutoSaveLocalStrictMode::restore() {
   MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_));
   sc_ = nullptr;
 }
 
 ClassEmitter::ClassEmitter(BytecodeEmitter* bce)
     : PropertyEmitter(bce),
       strictMode_(bce->sc),
-      name_(bce->cx),
-      nameForAnonymousClass_(bce->cx) {
+      name_(nullptr),
+      nameForAnonymousClass_(nullptr) {
   isClass_ = true;
 }
 
-bool ClassEmitter::emitScope(JS::Handle<LexicalScope::Data*> scopeBindings) {
+bool ClassEmitter::emitScope(ParserLexicalScopeData* scopeBindings) {
   MOZ_ASSERT(propertyState_ == PropertyState::Start);
   MOZ_ASSERT(classState_ == ClassState::Start);
 
   tdzCache_.emplace(bce_);
 
   innerScope_.emplace(bce_);
   if (!innerScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) {
     return false;
@@ -440,18 +441,17 @@ bool ClassEmitter::emitScope(JS::Handle<
 
 #ifdef DEBUG
   classState_ = ClassState::Scope;
 #endif
 
   return true;
 }
 
-bool ClassEmitter::emitBodyScope(
-    JS::Handle<LexicalScope::Data*> scopeBindings) {
+bool ClassEmitter::emitBodyScope(ParserLexicalScopeData* scopeBindings) {
   MOZ_ASSERT(propertyState_ == PropertyState::Start);
   MOZ_ASSERT(classState_ == ClassState::Start ||
              classState_ == ClassState::Scope);
 
   bodyTdzCache_.emplace(bce_);
 
   bodyScope_.emplace(bce_);
   if (!bodyScope_->enterLexical(bce_, ScopeKind::ClassBody, scopeBindings)) {
@@ -460,18 +460,18 @@ bool ClassEmitter::emitBodyScope(
 
 #ifdef DEBUG
   classState_ = ClassState::BodyScope;
 #endif
 
   return true;
 }
 
-bool ClassEmitter::emitClass(JS::Handle<JSAtom*> name,
-                             JS::Handle<JSAtom*> nameForAnonymousClass,
+bool ClassEmitter::emitClass(const ParserAtom* name,
+                             const ParserAtom* nameForAnonymousClass,
                              bool hasNameOnStack) {
   MOZ_ASSERT(propertyState_ == PropertyState::Start);
   MOZ_ASSERT(classState_ == ClassState::Start ||
              classState_ == ClassState::Scope ||
              classState_ == ClassState::BodyScope);
   MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name);
   MOZ_ASSERT(!(nameForAnonymousClass && hasNameOnStack));
 
@@ -488,18 +488,18 @@ bool ClassEmitter::emitClass(JS::Handle<
   }
 
 #ifdef DEBUG
   classState_ = ClassState::Class;
 #endif
   return true;
 }
 
-bool ClassEmitter::emitDerivedClass(JS::Handle<JSAtom*> name,
-                                    JS::Handle<JSAtom*> nameForAnonymousClass,
+bool ClassEmitter::emitDerivedClass(const ParserAtom* name,
+                                    const ParserAtom* nameForAnonymousClass,
                                     bool hasNameOnStack) {
   MOZ_ASSERT(propertyState_ == PropertyState::Start);
   MOZ_ASSERT(classState_ == ClassState::Start ||
              classState_ == ClassState::Scope ||
              classState_ == ClassState::BodyScope);
   MOZ_ASSERT_IF(nameForAnonymousClass || hasNameOnStack, !name);
   MOZ_ASSERT(!nameForAnonymousClass || !hasNameOnStack);
 
@@ -611,17 +611,17 @@ bool ClassEmitter::emitInitConstructor(b
   return true;
 }
 
 bool ClassEmitter::emitInitDefaultConstructor(uint32_t classStart,
                                               uint32_t classEnd) {
   MOZ_ASSERT(propertyState_ == PropertyState::Start);
   MOZ_ASSERT(classState_ == ClassState::Class);
 
-  RootedAtom className(bce_->cx, name_);
+  const ParserAtom* className = name_;
   if (!className) {
     if (nameForAnonymousClass_) {
       className = nameForAnonymousClass_;
     } else {
       className = bce_->cx->parserNames().empty;
     }
   }
 
@@ -702,19 +702,19 @@ bool ClassEmitter::prepareForMemberIniti
                                                 bool isStatic) {
   MOZ_ASSERT_IF(!isStatic, classState_ == ClassState::Class);
   MOZ_ASSERT_IF(isStatic, classState_ == ClassState::InitConstructor);
   MOZ_ASSERT(memberState_ == MemberState::Start);
 
   // .initializers is a variable that stores an array of lambdas containing
   // code (the initializer) for each field. Upon an object's construction,
   // these lambdas will be called, defining the values.
-  auto initializersName = isStatic ? &JSAtomState::dotStaticInitializers
-                                   : &JSAtomState::dotInitializers;
-  HandlePropertyName initializers = bce_->cx->parserNames().*initializersName;
+  const ParserName* initializers =
+      isStatic ? bce_->cx->parserNames().dotStaticInitializers
+               : bce_->cx->parserNames().dotInitializers;
   initializersAssignment_.emplace(bce_, initializers,
                                   NameOpEmitter::Kind::Initialize);
   if (!initializersAssignment_->prepareForRhs()) {
     return false;
   }
 
   if (!bce_->emitUint32Operand(JSOp::NewArray, numInitializers)) {
     //              [stack] ARRAY
--- a/js/src/frontend/ObjectEmitter.h
+++ b/js/src/frontend/ObjectEmitter.h
@@ -13,19 +13,17 @@
 #include <stddef.h>  // size_t
 #include <stdint.h>  // uint32_t
 
 #include "frontend/BytecodeOffset.h"  // BytecodeOffset
 #include "frontend/EmitterScope.h"    // EmitterScope
 #include "frontend/NameOpEmitter.h"   // NameOpEmitter
 #include "frontend/ParseNode.h"       // AccessorType
 #include "frontend/TDZCheckCache.h"   // TDZCheckCache
-#include "js/RootingAPI.h"            // JS::Handle, JS::Rooted
 #include "vm/BytecodeUtil.h"          // JSOp
-#include "vm/JSAtom.h"                // JSAtom
 #include "vm/NativeObject.h"          // PlainObject
 #include "vm/Scope.h"                 // LexicalScope
 
 namespace js {
 
 namespace frontend {
 
 struct BytecodeEmitter;
@@ -230,29 +228,29 @@ class MOZ_STACK_CLASS PropertyEmitter {
   MOZ_MUST_USE bool prepareForComputedPropKey(
       const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype);
   MOZ_MUST_USE bool prepareForComputedPropValue();
 
   MOZ_MUST_USE bool emitInitHomeObject();
 
   // @param key
   //        Property key
-  MOZ_MUST_USE bool emitInit(AccessorType accessorType, HandleAtom key);
+  MOZ_MUST_USE bool emitInit(AccessorType accessorType, const ParserAtom* key);
 
   MOZ_MUST_USE bool emitInitIndexOrComputed(AccessorType accessorType);
 
  private:
   MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp(
       const mozilla::Maybe<uint32_t>& keyPos, bool isStatic, bool isComputed);
 
   // @param op
   //        Opcode for initializing property
   // @param key
   //        Atom of the property if the property key is not computed
-  MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle<JSAtom*> key);
+  MOZ_MUST_USE bool emitInit(JSOp op, const ParserAtom* key);
   MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op);
 
   MOZ_MUST_USE bool emitPopClassConstructor();
 };
 
 // Class for emitting bytecode for object literal.
 //
 // Usage: (check for the return value is omitted for simplicity)
@@ -750,40 +748,39 @@ class MOZ_STACK_CLASS ClassEmitter : pub
     // After calling emitMemberInitializerHomeObject
     InitializerWithHomeObject,
   };
   MemberState memberState_ = MemberState::Start;
 
   size_t numInitializers_ = 0;
 #endif
 
-  JS::Rooted<JSAtom*> name_;
-  JS::Rooted<JSAtom*> nameForAnonymousClass_;
+  const ParserAtom* name_;
+  const ParserAtom* nameForAnonymousClass_;
   bool hasNameOnStack_ = false;
   mozilla::Maybe<NameOpEmitter> initializersAssignment_;
   size_t initializerIndex_ = 0;
 
  public:
   explicit ClassEmitter(BytecodeEmitter* bce);
 
-  bool emitScope(JS::Handle<LexicalScope::Data*> scopeBindings);
-
-  bool emitBodyScope(JS::Handle<LexicalScope::Data*> scopeBindings);
+  bool emitScope(ParserLexicalScopeData* scopeBindings);
+  bool emitBodyScope(ParserLexicalScopeData* scopeBindings);
 
   // @param name
   //        Name of the class (nullptr if this is anonymous class)
   // @param nameForAnonymousClass
   //        Statically inferred name of the class (only for anonymous classes)
   // @param hasNameOnStack
   //        If true the name is on the stack (only for anonymous classes)
-  MOZ_MUST_USE bool emitClass(JS::Handle<JSAtom*> name,
-                              JS::Handle<JSAtom*> nameForAnonymousClass,
+  MOZ_MUST_USE bool emitClass(const ParserAtom* name,
+                              const ParserAtom* nameForAnonymousClass,
                               bool hasNameOnStack);
-  MOZ_MUST_USE bool emitDerivedClass(JS::Handle<JSAtom*> name,
-                                     JS::Handle<JSAtom*> nameForAnonymousClass,
+  MOZ_MUST_USE bool emitDerivedClass(const ParserAtom* name,
+                                     const ParserAtom* nameForAnonymousClass,
                                      bool hasNameOnStack);
 
   // @param needsHomeObject
   //        True if the constructor contains `super.foo`
   MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject);
 
   // Parameters are the offset in the source code for each character below:
   //
--- a/js/src/frontend/ParseContext-inl.h
+++ b/js/src/frontend/ParseContext-inl.h
@@ -58,17 +58,17 @@ inline ParseContext::VarScope::VarScope(
 
 inline ParseContext::VarScope::VarScope(JSContext* cx, ParseContext* pc,
                                         UsedNameTracker& usedNames)
     : Scope(cx, pc, usedNames) {
   useAsVarScope(pc);
 }
 
 inline JS::Result<Ok, ParseContext::BreakStatementError>
-ParseContext::checkBreakStatement(PropertyName* label) {
+ParseContext::checkBreakStatement(const ParserName* label) {
   // Labeled 'break' statements target the nearest labeled statements (could
   // be any kind) with the same label. Unlabeled 'break' statements target
   // the innermost loop or switch statement.
   if (label) {
     auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
       MOZ_ASSERT(stmt);
       return stmt->label() == label;
     };
@@ -86,17 +86,17 @@ ParseContext::checkBreakStatement(Proper
       return mozilla::Err(ParseContext::BreakStatementError::ToughBreak);
     }
   }
 
   return Ok();
 }
 
 inline JS::Result<Ok, ParseContext::ContinueStatementError>
-ParseContext::checkContinueStatement(PropertyName* label) {
+ParseContext::checkContinueStatement(const ParserName* label) {
   // Labeled 'continue' statements target the nearest labeled loop
   // statements with the same label. Unlabeled 'continue' statements target
   // the innermost loop statement.
   auto isLoop = [](ParseContext::Statement* stmt) {
     MOZ_ASSERT(stmt);
     return StatementKindIsLoop(stmt->kind());
   };
 
--- a/js/src/frontend/ParseContext.cpp
+++ b/js/src/frontend/ParseContext.cpp
@@ -57,17 +57,17 @@ bool DeclarationKindIsVar(DeclarationKin
          kind == DeclarationKind::VarForAnnexBLexicalFunction;
 }
 
 bool DeclarationKindIsParameter(DeclarationKind kind) {
   return kind == DeclarationKind::PositionalFormalParameter ||
          kind == DeclarationKind::FormalParameter;
 }
 
-bool UsedNameTracker::noteUse(JSContext* cx, JSAtom* name,
+bool UsedNameTracker::noteUse(JSContext* cx, const ParserAtom* name,
                               NameVisibility visibility, uint32_t scriptId,
                               uint32_t scopeId,
                               mozilla::Maybe<TokenPos> tokenPosition) {
   if (UsedNameMap::AddPtr p = map_.lookupForAdd(name)) {
     if (!p->value().noteUsedInScope(scriptId, scopeId)) {
       return false;
     }
   } else {
@@ -156,17 +156,17 @@ void UsedNameTracker::rewind(RewindToken
 
 void ParseContext::Scope::dump(ParseContext* pc) {
   JSContext* cx = pc->sc()->cx_;
 
   fprintf(stdout, "ParseScope %p", this);
 
   fprintf(stdout, "\n  decls:\n");
   for (DeclaredNameMap::Range r = declared_->all(); !r.empty(); r.popFront()) {
-    UniqueChars bytes = AtomToPrintableString(cx, r.front().key());
+    UniqueChars bytes = QuoteString(cx, r.front().key());
     if (!bytes) {
       return;
     }
     DeclaredNameInfo& info = r.front().value().wrapped;
     fprintf(stdout, "    %s %s%s\n", DeclarationKindString(info.kind()),
             bytes.get(), info.closedOver() ? " (closed over)" : "");
   }
 
@@ -190,22 +190,21 @@ bool ParseContext::Scope::propagateAndMa
   if (pc->sc()->strict() || !possibleAnnexBFunctionBoxes_ ||
       possibleAnnexBFunctionBoxes_->empty()) {
     return true;
   }
 
   if (this == &pc->varScope()) {
     // Base case: actually declare the Annex B vars and mark applicable
     // function boxes as Annex B.
-    RootedPropertyName name(pc->sc()->cx_);
     Maybe<DeclarationKind> redeclaredKind;
     uint32_t unused;
     for (FunctionBox* funbox : *possibleAnnexBFunctionBoxes_) {
       if (pc->annexBAppliesToLexicalFunctionInInnermostScope(funbox)) {
-        name = funbox->explicitName()->asPropertyName();
+        const ParserName* name = funbox->explicitName()->asName();
         if (!pc->tryDeclareVar(
                 name, DeclarationKind::VarForAnnexBLexicalFunction,
                 DeclaredNameInfo::npos, &redeclaredKind, &unused)) {
           return false;
         }
 
         MOZ_ASSERT(!redeclaredKind);
         funbox->isAnnexB = true;
@@ -237,17 +236,17 @@ bool ParseContext::Scope::addCatchParame
     return true;
   }
 
   for (DeclaredNameMap::Range r = catchParamScope.declared_->all(); !r.empty();
        r.popFront()) {
     DeclarationKind kind = r.front().value()->kind();
     uint32_t pos = r.front().value()->pos();
     MOZ_ASSERT(DeclarationKindIsCatchParameter(kind));
-    JSAtom* name = r.front().key();
+    const ParserAtom* name = r.front().key();
     AddDeclaredNamePtr p = lookupDeclaredNameForAdd(name);
     MOZ_ASSERT(!p);
     if (!addDeclaredName(pc, p, name, kind, pos)) {
       return false;
     }
   }
 
   return true;
@@ -343,17 +342,17 @@ bool ParseContext::init() {
 
   return true;
 }
 
 bool ParseContext::annexBAppliesToLexicalFunctionInInnermostScope(
     FunctionBox* funbox) {
   MOZ_ASSERT(!sc()->strict());
 
-  RootedPropertyName name(sc()->cx_, funbox->explicitName()->asPropertyName());
+  const ParserName* name = funbox->explicitName()->asName();
   Maybe<DeclarationKind> redeclaredKind = isVarRedeclaredInInnermostScope(
       name, DeclarationKind::VarForAnnexBLexicalFunction);
 
   if (!redeclaredKind && isFunctionBox()) {
     Scope& funScope = functionScope();
     if (&funScope != &varScope()) {
       // Annex B.3.3.1 disallows redeclaring parameter names. In the
       // presence of parameter expressions, parameter names are on the
@@ -372,82 +371,94 @@ bool ParseContext::annexBAppliesToLexica
   }
 
   // If an early error would have occurred already, this function should not
   // exhibit Annex B.3.3 semantics.
   return !redeclaredKind;
 }
 
 Maybe<DeclarationKind> ParseContext::isVarRedeclaredInInnermostScope(
-    HandlePropertyName name, DeclarationKind kind) {
+    const ParserName* name, DeclarationKind kind) {
   Maybe<DeclarationKind> redeclaredKind;
   uint32_t unused;
   MOZ_ALWAYS_TRUE(tryDeclareVarHelper<DryRunInnermostScopeOnly>(
       name, kind, DeclaredNameInfo::npos, &redeclaredKind, &unused));
   return redeclaredKind;
 }
 
-Maybe<DeclarationKind> ParseContext::isVarRedeclaredInEval(
-    HandlePropertyName name, DeclarationKind kind) {
+bool ParseContext::isVarRedeclaredInEval(const ParserName* name,
+                                         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.
+  auto mbNameAtom = name->toJSAtom(sc()->cx_);
+  if (mbNameAtom.isErr()) {
+    return false;
+  }
+  JSAtom* nameAtom = mbNameAtom.unwrap();
+
   // 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()->compilationInfo().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() != name) {
+      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) {
-            return Some(ScopeKindIsCatch(si.kind())
+            *out = Some(ScopeKindIsCatch(si.kind())
                             ? DeclarationKind::CatchParameter
                             : DeclarationKind::Let);
+            return true;
           }
           break;
         }
 
         case BindingKind::Const:
-          return Some(DeclarationKind::Const);
+          *out = Some(DeclarationKind::Const);
+          return true;
 
         case BindingKind::Import:
         case BindingKind::FormalParameter:
         case BindingKind::Var:
         case BindingKind::NamedLambdaCallee:
           break;
       }
     }
 
     if (si.scope() == varScope) {
       break;
     }
   }
 
-  return Nothing();
+  *out = Nothing();
+  return true;
 }
 
-bool ParseContext::tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
+bool ParseContext::tryDeclareVar(const ParserName* name, DeclarationKind kind,
                                  uint32_t beginPos,
                                  Maybe<DeclarationKind>* redeclaredKind,
                                  uint32_t* prevPos) {
   return tryDeclareVarHelper<NotDryRun>(name, kind, beginPos, redeclaredKind,
                                         prevPos);
 }
 
 template <ParseContext::DryRunOption dryRunOption>
-bool ParseContext::tryDeclareVarHelper(HandlePropertyName name,
+bool ParseContext::tryDeclareVarHelper(const ParserName* name,
                                        DeclarationKind kind, uint32_t beginPos,
                                        Maybe<DeclarationKind>* redeclaredKind,
                                        uint32_t* prevPos) {
   MOZ_ASSERT(DeclarationKindIsVar(kind));
 
   // It is an early error if a 'var' declaration appears inside a
   // scope contour that has a lexical declaration of the same name. For
   // example, the following are early errors:
@@ -507,34 +518,36 @@ bool ParseContext::tryDeclareVarHelper(H
     // the innermost scope when doing a dry run.
     if (dryRunOption == DryRunInnermostScopeOnly) {
       break;
     }
   }
 
   if (!sc()->strict() && sc()->isEvalContext() &&
       (dryRunOption == NotDryRun || innermostScope() == &varScope())) {
-    *redeclaredKind = isVarRedeclaredInEval(name, kind);
+    if (!isVarRedeclaredInEval(name, kind, redeclaredKind)) {
+      return false;
+    }
     // We don't have position information at runtime.
     *prevPos = DeclaredNameInfo::npos;
   }
 
   return true;
 }
 
 bool ParseContext::hasUsedName(const UsedNameTracker& usedNames,
-                               HandlePropertyName name) {
+                               const ParserName* name) {
   if (auto p = usedNames.lookup(name)) {
     return p->value().isUsedInScript(scriptId());
   }
   return false;
 }
 
 bool ParseContext::hasUsedFunctionSpecialName(const UsedNameTracker& usedNames,
-                                              HandlePropertyName name) {
+                                              const ParserName* name) {
   MOZ_ASSERT(name == sc()->cx_->parserNames().arguments ||
              name == sc()->cx_->parserNames().dotThis);
   return hasUsedName(usedNames, name) ||
          functionBox()->bindingsAccessedDynamically();
 }
 
 bool ParseContext::declareFunctionThis(const UsedNameTracker& usedNames,
                                        bool canSkipLazyClosedOverBindings) {
@@ -542,17 +555,17 @@ bool ParseContext::declareFunctionThis(c
   // optimization, avoid doing any work here.
   if (useAsmOrInsideUseAsm()) {
     return true;
   }
 
   // Derived class constructors emit JSOp::CheckReturn, which requires
   // '.this' to be bound.
   FunctionBox* funbox = functionBox();
-  HandlePropertyName dotThis = sc()->cx_->parserNames().dotThis;
+  const ParserName* dotThis = sc()->cx_->parserNames().dotThis;
 
   bool declareThis;
   if (canSkipLazyClosedOverBindings) {
     declareThis = funbox->functionHasThisBinding();
   } else {
     declareThis = hasUsedFunctionSpecialName(usedNames, dotThis) ||
                   funbox->isClassConstructor();
   }
@@ -576,17 +589,17 @@ bool ParseContext::declareFunctionArgume
   FunctionBox* funbox = functionBox();
   ParseContext::Scope& funScope = functionScope();
   ParseContext::Scope& _varScope = varScope();
 
   bool usesArguments = false;
   bool hasExtraBodyVarScope = &funScope != &_varScope;
 
   // Time to implement the odd semantics of 'arguments'.
-  HandlePropertyName argumentsName = sc()->cx_->parserNames().arguments;
+  const ParserName* argumentsName = sc()->cx_->parserNames().arguments;
 
   bool tryDeclareArguments;
   if (canSkipLazyClosedOverBindings) {
     tryDeclareArguments = funbox->shouldDeclareArguments();
   } else {
     tryDeclareArguments = hasUsedFunctionSpecialName(usedNames, argumentsName);
   }
 
@@ -640,17 +653,17 @@ bool ParseContext::declareFunctionArgume
 
   return true;
 }
 
 bool ParseContext::declareDotGeneratorName() {
   // The special '.generator' binding must be on the function scope, as
   // generators expect to find it on the CallObject.
   ParseContext::Scope& funScope = functionScope();
-  HandlePropertyName dotGenerator = sc()->cx_->parserNames().dotGenerator;
+  const ParserName* dotGenerator = sc()->cx_->parserNames().dotGenerator;
   AddDeclaredNamePtr p = funScope.lookupDeclaredNameForAdd(dotGenerator);
   if (!p &&
       !funScope.addDeclaredName(this, p, dotGenerator, DeclarationKind::Var,
                                 DeclaredNameInfo::npos)) {
     return false;
   }
   return true;
 }
--- a/js/src/frontend/ParseContext.h
+++ b/js/src/frontend/ParseContext.h
@@ -64,23 +64,23 @@ class ParseContext : public Nestable<Par
       MOZ_ASSERT(kind_ == StatementKind::ForLoop);
       MOZ_ASSERT(newForKind == StatementKind::ForInLoop ||
                  newForKind == StatementKind::ForOfLoop);
       kind_ = newForKind;
     }
   };
 
   class LabelStatement : public Statement {
-    RootedAtom label_;
+    const ParserAtom* label_;
 
    public:
-    LabelStatement(ParseContext* pc, JSAtom* label)
-        : Statement(pc, StatementKind::Label), label_(pc->sc_->cx_, label) {}
+    LabelStatement(ParseContext* pc, const ParserAtom* label)
+        : Statement(pc, StatementKind::Label), label_(label) {}
 
-    HandleAtom label() const { return label_; }
+    const ParserAtom* label() const { return label_; }
   };
 
   struct ClassStatement : public Statement {
     FunctionBox* constructorBox;
 
     explicit ClassStatement(ParseContext* pc)
         : Statement(pc, StatementKind::Class), constructorBox(nullptr) {}
   };
@@ -135,27 +135,27 @@ class ParseContext : public Nestable<Par
         return false;
       }
 
       return declared_.acquire(pc->sc()->cx_);
     }
 
     bool isEmpty() const { return declared_->all().empty(); }
 
-    DeclaredNamePtr lookupDeclaredName(JSAtom* name) {
+    DeclaredNamePtr lookupDeclaredName(const ParserAtom* name) {
       return declared_->lookup(name);
     }
 
-    AddDeclaredNamePtr lookupDeclaredNameForAdd(JSAtom* name) {
+    AddDeclaredNamePtr lookupDeclaredNameForAdd(const ParserAtom* name) {
       return declared_->lookupForAdd(name);
     }
 
     MOZ_MUST_USE bool addDeclaredName(ParseContext* pc, AddDeclaredNamePtr& p,
-                                      JSAtom* name, DeclarationKind kind,
-                                      uint32_t pos) {
+                                      const ParserAtom* name,
+                                      DeclarationKind kind, uint32_t pos) {
       return maybeReportOOM(
           pc, declared_->add(p, name, DeclaredNameInfo(kind, pos)));
     }
 
     // Add a FunctionBox as a possible candidate for Annex B.3.3 semantics.
     MOZ_MUST_USE bool addPossibleAnnexBFunctionBox(ParseContext* pc,
                                                    FunctionBox* funbox);
 
@@ -207,17 +207,17 @@ class ParseContext : public Nestable<Par
         }
       }
 
      public:
       bool done() const { return declaredRange_.empty(); }
 
       explicit operator bool() const { return !done(); }
 
-      JSAtom* name() {
+      const ParserAtom* name() {
         MOZ_ASSERT(!done());
         return declaredRange_.front().key();
       }
 
       DeclarationKind declarationKind() {
         MOZ_ASSERT(!done());
         return declaredRange_.front().value()->kind();
       }
@@ -397,24 +397,24 @@ class ParseContext : public Nestable<Par
     // Unlabeled break must be inside loop or switch.
     ToughBreak,
     LabelNotFound,
   };
 
   // Return Err(true) if we have encountered at least one loop,
   // Err(false) otherwise.
   MOZ_MUST_USE inline JS::Result<Ok, BreakStatementError> checkBreakStatement(
-      PropertyName* label);
+      const ParserName* label);
 
   enum class ContinueStatementError {
     NotInALoop,
     LabelNotFound,
   };
   MOZ_MUST_USE inline JS::Result<Ok, ContinueStatementError>
-  checkContinueStatement(PropertyName* label);
+  checkContinueStatement(const ParserName* label);
 
   // True if we are at the topmost level of a entire script or function body.
   // For example, while parsing this code we would encounter f1 and f2 at
   // body level, but we would not encounter f3 or f4 at body level:
   //
   //   function f1() { function f2() { } }
   //   if (cond) { function f3() { if (cond) { function f4() { } } } }
   //
@@ -475,40 +475,42 @@ class ParseContext : public Nestable<Par
     return sc_->isFunctionBox() && (sc_->asFunctionBox()->isGetter() ||
                                     sc_->asFunctionBox()->isSetter());
   }
 
   uint32_t scriptId() const { return scriptId_; }
 
   bool annexBAppliesToLexicalFunctionInInnermostScope(FunctionBox* funbox);
 
-  bool tryDeclareVar(HandlePropertyName name, DeclarationKind kind,
+  bool tryDeclareVar(const ParserName* name, DeclarationKind kind,
                      uint32_t beginPos,
                      mozilla::Maybe<DeclarationKind>* redeclaredKind,
                      uint32_t* prevPos);
 
-  bool hasUsedName(const UsedNameTracker& usedNames, HandlePropertyName name);
+  bool hasUsedName(const UsedNameTracker& usedNames, const ParserName* name);
   bool hasUsedFunctionSpecialName(const UsedNameTracker& usedNames,
-                                  HandlePropertyName name);
+                                  const ParserName* name);
 
   bool declareFunctionThis(const UsedNameTracker& usedNames,
                            bool canSkipLazyClosedOverBindings);
   bool declareFunctionArgumentsObject(const UsedNameTracker& usedNames,
                                       bool canSkipLazyClosedOverBindings);
   bool declareDotGeneratorName();
 
  private:
   mozilla::Maybe<DeclarationKind> isVarRedeclaredInInnermostScope(
-      HandlePropertyName name, DeclarationKind kind);
-  mozilla::Maybe<DeclarationKind> isVarRedeclaredInEval(HandlePropertyName name,
-                                                        DeclarationKind kind);
+      const ParserName* name, DeclarationKind kind);
+
+  MOZ_MUST_USE bool isVarRedeclaredInEval(const ParserName* name,
+                                          DeclarationKind kind,
+                                          mozilla::Maybe<DeclarationKind>* out);
 
   enum DryRunOption { NotDryRun, DryRunInnermostScopeOnly };
   template <DryRunOption dryRunOption>
-  bool tryDeclareVarHelper(HandlePropertyName name, DeclarationKind kind,
+  bool tryDeclareVarHelper(const ParserName* name, DeclarationKind kind,
                            uint32_t beginPos,
                            mozilla::Maybe<DeclarationKind>* redeclaredKind,
                            uint32_t* prevPos);
 };
 
 }  // namespace frontend
 
 }  // namespace js
--- a/js/src/frontend/ParseNode.cpp
+++ b/js/src/frontend/ParseNode.cpp
@@ -194,22 +194,33 @@ void NumericLiteral::dumpImpl(GenericPri
 void BigIntLiteral::dumpImpl(GenericPrinter& out, int indent) {
   out.printf("(%s)", parseNodeNames[getKindAsIndex()]);
 }
 
 void RegExpLiteral::dumpImpl(GenericPrinter& out, int indent) {
   out.printf("(%s)", parseNodeNames[getKindAsIndex()]);
 }
 
+static void DumpCharsNoNewline(const ParserAtom* atom,
+                               js::GenericPrinter& out) {
+  if (atom->hasLatin1Chars()) {
+    out.put("[Latin 1]");
+    JSString::dumpChars(atom->latin1Chars(), atom->length(), out);
+  } else {
+    out.put("[2 byte]");
+    JSString::dumpChars(atom->twoByteChars(), atom->length(), out);
+  }
+}
+
 void LoopControlStatement::dumpImpl(GenericPrinter& out, int indent) {
   const char* name = parseNodeNames[getKindAsIndex()];
   out.printf("(%s", name);
   if (label()) {
     out.printf(" ");
-    label()->dumpCharsNoNewline(out);
+    DumpCharsNoNewline(label(), out);
   }
   out.printf(")");
 }
 
 void UnaryNode::dumpImpl(GenericPrinter& out, int indent) {
   const char* name = parseNodeNames[getKindAsIndex()];
   out.printf("(%s ", name);
   indent += strlen(name) + 2;
@@ -303,31 +314,30 @@ static void DumpName(GenericPrinter& out
   }
 }
 
 void NameNode::dumpImpl(GenericPrinter& out, int indent) {
   switch (getKind()) {
     case ParseNodeKind::StringExpr:
     case ParseNodeKind::TemplateStringExpr:
     case ParseNodeKind::ObjectPropertyName:
-      atom()->dumpCharsNoNewline(out);
+      DumpCharsNoNewline(atom(), out);
       return;
 
     case ParseNodeKind::Name:
     case ParseNodeKind::PrivateName:  // atom() already includes the '#', no
                                       // need to specially include it.
     case ParseNodeKind::PropertyNameExpr:
       if (!atom()) {
         out.put("#<null name>");
       } else {
-        JS::AutoCheckCannotGC nogc;
         if (atom()->hasLatin1Chars()) {
-          DumpName(out, atom()->latin1Chars(nogc), atom()->length());
+          DumpName(out, atom()->latin1Chars(), atom()->length());
         } else {
-          DumpName(out, atom()->twoByteChars(nogc), atom()->length());
+          DumpName(out, atom()->twoByteChars(), atom()->length());
         }
       }
       return;
 
     case ParseNodeKind::LabelStmt: {
       this->as<LabeledStatement>().dumpImpl(out, indent);
       return;
     }
@@ -338,36 +348,35 @@ void NameNode::dumpImpl(GenericPrinter& 
       return;
     }
   }
 }
 
 void LabeledStatement::dumpImpl(GenericPrinter& out, int indent) {
   const char* name = parseNodeNames[getKindAsIndex()];
   out.printf("(%s ", name);
-  atom()->dumpCharsNoNewline(out);
+  DumpCharsNoNewline(atom(), out);
   out.printf(" ");
   indent += strlen(name) + atom()->length() + 3;
   DumpParseTree(statement(), out, indent);
   out.printf(")");
 }
 
 void LexicalScopeNode::dumpImpl(GenericPrinter& out, int indent) {
   const char* name = parseNodeNames[getKindAsIndex()];
   out.printf("(%s [", name);
   int nameIndent = indent + strlen(name) + 3;
   if (!isEmptyScope()) {
-    LexicalScope::Data* bindings = scopeBindings();
+    ParserScopeData<LexicalScope>* bindings = scopeBindings();
     for (uint32_t i = 0; i < bindings->length; i++) {
-      JSAtom* name = bindings->trailingNames[i].name();
-      JS::AutoCheckCannotGC nogc;
+      const ParserAtom* name = bindings->trailingNames[i].name();
       if (name->hasLatin1Chars()) {
-        DumpName(out, name->latin1Chars(nogc), name->length());
+        DumpName(out, name->latin1Chars(), name->length());
       } else {
-        DumpName(out, name->twoByteChars(nogc), name->length());
+        DumpName(out, name->twoByteChars(), name->length());
       }
       if (i < bindings->length - 1) {
         IndentNewLine(out, nameIndent);
       }
     }
   }
   out.putChar(']');
   indent += 2;
@@ -380,18 +389,19 @@ void LexicalScopeNode::dumpImpl(GenericP
 BigInt* BigIntLiteral::create(JSContext* cx) {
   return compilationInfo_.bigIntData[index_].createBigInt(cx);
 }
 
 bool BigIntLiteral::isZero() {
   return compilationInfo_.bigIntData[index_].isZero();
 }
 
-JSAtom* NumericLiteral::toAtom(JSContext* cx) const {
-  return NumberToAtom(cx, value());
+const ParserAtom* NumericLiteral::toAtom(
+    CompilationInfo& compilationInfo) const {
+  return NumberToParserAtom(compilationInfo, value());
 }
 
 RegExpObject* RegExpStencil::createRegExp(JSContext* cx) const {
   MOZ_ASSERT(buf_);
   return RegExpObject::createSyntaxChecked(cx, buf_.get(), length_, flags_,
                                            TenuredObject);
 }
 
--- a/js/src/frontend/ParseNode.h
+++ b/js/src/frontend/ParseNode.h
@@ -13,16 +13,17 @@
 #include <iterator>
 #include <stddef.h>
 #include <stdint.h>
 
 #include "jstypes.h"  // js::Bit
 
 #include "frontend/FunctionSyntaxKind.h"  // FunctionSyntaxKind
 #include "frontend/NameAnalysisTypes.h"   // PrivateNameKind
+#include "frontend/ParserAtom.h"
 #include "frontend/Stencil.h"
 #include "frontend/Token.h"
 #include "js/RootingAPI.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Scope.h"
 #include "vm/ScopeKind.h"
 #include "vm/StringType.h"
 
@@ -33,26 +34,19 @@
 //   can be reimplemented with a custom `new_`.
 //
 // - The tree is bulk-deallocated when the parser is deallocated. Consequently,
 //   references to a subtree MUST NOT exist once the parser has been
 //   deallocated.
 //
 // - This bulk-deallocation DOES NOT run destructors.
 //
-// - Instances of `LexicalScope::Data` MUST BE allocated as instances of
-//   `ParseNode`, in the same `LifoAlloc`. They are bulk-deallocated alongside
-//   the rest of the tree.
-//
-// - Instances of `JSAtom` used throughout the tree (including instances of
-//   `PropertyName`) MUST be kept alive by the parser. This is done through an
-//   instance of `AutoKeepAtoms` held by the parser.
-//
-// - Once the parser is deallocated, the `JSAtom` instances MAY be
-//   garbage-collected.
+// - Instances of `ParserScopeData<LexicalScope>` MUST BE allocated as
+//   instances of `ParseNode`, in the same `LifoAlloc`. They are bulk-
+//   deallocated alongside the rest of the tree.
 
 struct JSContext;
 
 namespace JS {
 class BigInt;
 }
 
 namespace js {
@@ -62,18 +56,25 @@ class LifoAlloc;
 class RegExpObject;
 
 namespace frontend {
 
 class ParseContext;
 struct CompilationInfo;
 class ParserSharedBase;
 class FullParseHandler;
+
 class FunctionBox;
 
+// This typedef unfortunately needs to be replicated here.
+using ParserBindingName = AbstractBindingName<const ParserAtom>;
+
+template <typename Scope>
+using ParserScopeData = typename Scope::template AbstractData<const ParserAtom>;
+
 #define FOR_EACH_PARSE_NODE_KIND(F)                              \
   F(EmptyStmt, NullaryNode)                                      \
   F(ExpressionStmt, UnaryNode)                                   \
   F(CommaExpr, ListNode)                                         \
   F(ConditionalExpr, ConditionalExpression)                      \
   F(PropertyDefinition, PropertyDefinition)                      \
   F(Shorthand, BinaryNode)                                       \
   F(PosExpr, UnaryNode)                                          \
@@ -742,17 +743,17 @@ class ParseNode {
  public:
   TypeCode typeCode() const { return typeCodeTable[getKindAsIndex()]; }
 
   bool isBinaryOperation() const {
     ParseNodeKind kind = getKind();
     return ParseNodeKind::BinOpFirst <= kind &&
            kind <= ParseNodeKind::BinOpLast;
   }
-  inline bool isName(PropertyName* name) const;
+  inline bool isName(const ParserName* name) const;
 
   /* Boolean attributes. */
   bool isInParens() const { return pn_parens; }
   bool isLikelyIIFE() const { return isInParens(); }
   void setInParens(bool enabled) { pn_parens = enabled; }
 
   bool isDirectRHSAnonFunction() const { return pn_rhs_anon_fun; }
   void setDirectRHSAnonFunction(bool enabled) { pn_rhs_anon_fun = enabled; }
@@ -845,21 +846,21 @@ class NullaryNode : public ParseNode {
   }
 
 #ifdef DEBUG
   void dumpImpl(GenericPrinter& out, int indent);
 #endif
 };
 
 class NameNode : public ParseNode {
-  JSAtom* atom_; /* lexical name or label atom */
+  const ParserAtom* atom_; /* lexical name or label atom */
   PrivateNameKind privateNameKind_ = PrivateNameKind::None;
 
  public:
-  NameNode(ParseNodeKind kind, JSAtom* atom, const TokenPos& pos)
+  NameNode(ParseNodeKind kind, const ParserAtom* atom, const TokenPos& pos)
       : ParseNode(kind, pos), atom_(atom) {
     MOZ_ASSERT(is<NameNode>());
   }
 
   static bool test(const ParseNode& node) {
     return node.typeCode() == TypeCode::Name;
   }
 
@@ -869,34 +870,34 @@ class NameNode : public ParseNode {
   bool accept(Visitor& visitor) {
     return true;
   }
 
 #ifdef DEBUG
   void dumpImpl(GenericPrinter& out, int indent);
 #endif
 
-  JSAtom* atom() const { return atom_; }
+  const ParserAtom* atom() const { return atom_; }
 
-  PropertyName* name() const {
+  const ParserName* name() const {
     MOZ_ASSERT(isKind(ParseNodeKind::Name) ||
                isKind(ParseNodeKind::PrivateName));
-    return atom()->asPropertyName();
+    return atom()->asName();
   }
 
-  void setAtom(JSAtom* atom) { atom_ = atom; }
+  void setAtom(const ParserAtom* atom) { atom_ = atom; }
 
   void setPrivateNameKind(PrivateNameKind privateNameKind) {
     privateNameKind_ = privateNameKind;
   }
 
   PrivateNameKind privateNameKind() { return privateNameKind_; }
 };
 
-inline bool ParseNode::isName(PropertyName* name) const {
+inline bool ParseNode::isName(const ParserName* name) const {
   return getKind() == ParseNodeKind::Name && as<NameNode>().name() == name;
 }
 
 class UnaryNode : public ParseNode {
   ParseNode* kid_;
 
  public:
   UnaryNode(ParseNodeKind kind, const TokenPos& pos, ParseNode* kid)
@@ -935,17 +936,17 @@ class UnaryNode : public ParseNode {
    * parsing, check the node's prologue flag to see if it is indeed part of
    * a directive prologue.
    *
    * Note that a Directive Prologue can contain statements that cannot
    * themselves be directives (string literals that include escape sequences
    * or escaped newlines, say). This member function returns true for such
    * nodes; we use it to determine the extent of the prologue.
    */
-  JSAtom* isStringExprStatement() const {
+  const ParserAtom* isStringExprStatement() const {
     if (isKind(ParseNodeKind::ExpressionStmt)) {
       if (kid()->isKind(ParseNodeKind::StringExpr) && !kid()->isInParens()) {
         return kid()->as<NameNode>().atom();
       }
     }
     return nullptr;
   }
 
@@ -1547,17 +1548,17 @@ class NumericLiteral : public ParseNode 
 
   DecimalPoint decimalPoint() const { return decimalPoint_; }
 
   void setValue(double v) { value_ = v; }
 
   void setDecimalPoint(DecimalPoint d) { decimalPoint_ = d; }
 
   // Return the decimal string representation of this numeric literal.
-  JSAtom* toAtom(JSContext* cx) const;
+  const ParserAtom* toAtom(CompilationInfo& compilationInfo) const;
 };
 
 class BigIntLiteral : public ParseNode {
   CompilationInfo& compilationInfo_;
   BigIntIndex index_;
 
  public:
   BigIntLiteral(BigIntIndex index, CompilationInfo& compilationInfo,
@@ -1585,22 +1586,22 @@ class BigIntLiteral : public ParseNode {
 
   // Create a BigInt value of this BigInt literal.
   BigInt* create(JSContext* cx);
 
   bool isZero();
 };
 
 class LexicalScopeNode : public ParseNode {
-  LexicalScope::Data* bindings;
+  ParserScopeData<LexicalScope>* bindings;
   ParseNode* body;
   ScopeKind kind_;
 
  public:
-  LexicalScopeNode(LexicalScope::Data* bindings, ParseNode* body,
+  LexicalScopeNode(ParserScopeData<LexicalScope>* bindings, ParseNode* body,
                    ScopeKind kind = ScopeKind::Lexical)
       : ParseNode(ParseNodeKind::LexicalScope, body->pn_pos),
         bindings(bindings),
         body(body),
         kind_(kind) {}
 
   static bool test(const ParseNode& node) {
     return node.isKind(ParseNodeKind::LexicalScope);
@@ -1612,42 +1613,42 @@ class LexicalScopeNode : public ParseNod
   bool accept(Visitor& visitor) {
     return visitor.visit(body);
   }
 
 #ifdef DEBUG
   void dumpImpl(GenericPrinter& out, int indent);
 #endif
 
-  Handle<LexicalScope::Data*> scopeBindings() const {
+  ParserScopeData<LexicalScope>* scopeBindings() const {
     MOZ_ASSERT(!isEmptyScope());
     // Bindings' GC safety depend on the presence of an AutoKeepAtoms that
     // the rest of the frontend also depends on.
-    return Handle<LexicalScope::Data*>::fromMarkedLocation(&bindings);
+    return bindings;
   }
 
   ParseNode* scopeBody() const { return body; }
 
   void setScopeBody(ParseNode* body) { this->body = body; }
 
   bool isEmptyScope() const { return !bindings; }
 
   ScopeKind kind() const { return kind_; }
 };
 
 class LabeledStatement : public NameNode {
   ParseNode* statement_;
 
  public:
-  LabeledStatement(PropertyName* label, ParseNode* stmt, uint32_t begin)
+  LabeledStatement(const ParserName* label, ParseNode* stmt, uint32_t begin)
       : NameNode(ParseNodeKind::LabelStmt, label,
                  TokenPos(begin, stmt->pn_pos.end)),
         statement_(stmt) {}
 
-  PropertyName* label() const { return atom()->asPropertyName(); }
+  const ParserName* label() const { return atom()->asName(); }
 
   ParseNode* statement() const { return statement_; }
 
   static bool test(const ParseNode& node) {
     return node.isKind(ParseNodeKind::LabelStmt);
   }
 
   template <typename Visitor>
@@ -1683,30 +1684,30 @@ class CaseClause : public BinaryNode {
   static bool test(const ParseNode& node) {
     bool match = node.isKind(ParseNodeKind::Case);
     MOZ_ASSERT_IF(match, node.is<BinaryNode>());
     return match;
   }
 };
 
 class LoopControlStatement : public ParseNode {
-  PropertyName* label_; /* target of break/continue statement */
+  const ParserName* label_; /* target of break/continue statement */
 
  protected:
-  LoopControlStatement(ParseNodeKind kind, PropertyName* label,
+  LoopControlStatement(ParseNodeKind kind, const ParserName* label,
                        const TokenPos& pos)
       : ParseNode(kind, pos), label_(label) {
     MOZ_ASSERT(kind == ParseNodeKind::BreakStmt ||
                kind == ParseNodeKind::ContinueStmt);
     MOZ_ASSERT(is<LoopControlStatement>());
   }
 
  public:
   /* Label associated with this break/continue statement, if any. */
-  PropertyName* label() const { return label_; }
+  const ParserName* label() const { return label_; }
 
 #ifdef DEBUG
   void dumpImpl(GenericPrinter& out, int indent);
 #endif
 
   static bool test(const ParseNode& node) {
     return node.isKind(ParseNodeKind::BreakStmt) ||
            node.isKind(ParseNodeKind::ContinueStmt);
@@ -1717,29 +1718,29 @@ class LoopControlStatement : public Pars
   template <typename Visitor>
   bool accept(Visitor& visitor) {
     return true;
   }
 };
 
 class BreakStatement : public LoopControlStatement {
  public:
-  BreakStatement(PropertyName* label, const TokenPos& pos)
+  BreakStatement(const ParserName* label, const TokenPos& pos)
       : LoopControlStatement(ParseNodeKind::BreakStmt, label, pos) {}
 
   static bool test(const ParseNode& node) {
     bool match = node.isKind(ParseNodeKind::BreakStmt);
     MOZ_ASSERT_IF(match, node.is<LoopControlStatement>());
     return match;
   }
 };
 
 class ContinueStatement : public LoopControlStatement {
  public:
-  ContinueStatement(PropertyName* label, const TokenPos& pos)
+  ContinueStatement(const ParserName* label, const TokenPos& pos)
       : LoopControlStatement(ParseNodeKind::ContinueStmt, label, pos) {}
 
   static bool test(const ParseNode& node) {
     bool match = node.isKind(ParseNodeKind::ContinueStmt);
     MOZ_ASSERT_IF(match, node.is<LoopControlStatement>());
     return match;
   }
 };
@@ -1917,18 +1918,18 @@ class PropertyAccessBase : public Binary
 
   // Method used by BytecodeEmitter::emitPropLHS for optimization.
   // Those methods allow expression to temporarily be nullptr for
   // optimization purpose.
   ParseNode* maybeExpression() const { return left(); }
 
   void setExpression(ParseNode* pn) { *unsafeLeftReference() = pn; }
 
-  PropertyName& name() const {
-    return *right()->as<NameNode>().atom()->asPropertyName();
+  const ParserName* name() const {
+    return right()->as<NameNode>().atom()->asName();
   }
 };
 
 class PropertyAccess : public PropertyAccessBase {
  public:
   PropertyAccess(ParseNode* lhs, NameNode* name, uint32_t begin, uint32_t end)
       : PropertyAccessBase(ParseNodeKind::DotExpr, lhs, name, begin, end) {
     MOZ_ASSERT(lhs);
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -83,17 +83,17 @@ using JS::RegExpFlags;
 
 namespace js::frontend {
 
 using DeclaredNamePtr = ParseContext::Scope::DeclaredNamePtr;
 using AddDeclaredNamePtr = ParseContext::Scope::AddDeclaredNamePtr;
 using BindingIter = ParseContext::Scope::BindingIter;
 using UsedNamePtr = UsedNameTracker::UsedNameMap::Ptr;
 
-using BindingNameVector = Vector<BindingName, 6>;
+using ParserBindingNameVector = Vector<ParserBindingName, 6>;
 
 template <class T, class U>
 static inline void PropagateTransitiveParseFlags(const T* inner, U* outer) {
   if (inner->bindingsAccessedDynamically()) {
     outer->setBindingsAccessedDynamically();
   }
   if (inner->hasDirectEval()) {
     outer->setHasDirectEval();
@@ -241,18 +241,18 @@ void Parser<FullParseHandler, Unit>::set
 template <class ParseHandler, typename Unit>
 inline void GeneralParser<ParseHandler, Unit>::setInParametersOfAsyncFunction(
     bool inParameters) {
   asFinalParser()->setInParametersOfAsyncFunction(inParameters);
 }
 
 template <class ParseHandler>
 FunctionBox* PerHandlerParser<ParseHandler>::newFunctionBox(
-    FunctionNodeType funNode, JSAtom* explicitName, FunctionFlags flags,
-    uint32_t toStringStart, Directives inheritedDirectives,
+    FunctionNodeType funNode, const ParserAtom* explicitName,
+    FunctionFlags flags, uint32_t toStringStart, Directives inheritedDirectives,
     GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
     TopLevelFunction isTopLevel) {
   MOZ_ASSERT(funNode);
 
   FunctionIndex index = FunctionIndex(compilationInfo_.funcData.length());
   MOZ_ASSERT_IF(isTopLevel == TopLevelFunction::Yes,
                 index == CompilationInfo::TopLevelFunctionIndex);
 
@@ -393,17 +393,17 @@ typename ParseHandler::ListNodeType Gene
 
   return stmtList;
 }
 
 /*
  * Strict mode forbids introducing new definitions for 'eval', 'arguments',
  * 'let', 'static', 'yield', or for any strict mode reserved word.
  */
-bool ParserBase::isValidStrictBinding(PropertyName* name) {
+bool ParserBase::isValidStrictBinding(const ParserName* name) {
   TokenKind tt = ReservedWordTokenKind(name);
   if (tt == TokenKind::Name) {
     return name != cx_->parserNames().eval &&
            name != cx_->parserNames().arguments;
   }
   return tt != TokenKind::Let && tt != TokenKind::Static &&
          tt != TokenKind::Yield && !TokenKindIsStrictReservedWord(tt);
 }
@@ -415,19 +415,19 @@ bool ParserBase::isValidStrictBinding(Pr
 bool ParserBase::hasValidSimpleStrictParameterNames() {
   MOZ_ASSERT(pc_->isFunctionBox() &&
              pc_->functionBox()->hasSimpleParameterList());
 
   if (pc_->functionBox()->hasDuplicateParameters) {
     return false;
   }
 
-  for (auto* name : pc_->positionalFormalParameterNames()) {
+  for (auto name : pc_->positionalFormalParameterNames()) {
     MOZ_ASSERT(name);
-    if (!isValidStrictBinding(name->asPropertyName())) {
+    if (!isValidStrictBinding(name->asName())) {
       return false;
     }
   }
   return true;
 }
 
 template <class ParseHandler, typename Unit>
 void GeneralParser<ParseHandler, Unit>::reportMissingClosing(
@@ -453,19 +453,19 @@ void GeneralParser<ParseHandler, Unit>::
     return;
   }
 
   errorWithNotes(std::move(notes), errorNumber);
 }
 
 template <class ParseHandler, typename Unit>
 void GeneralParser<ParseHandler, Unit>::reportRedeclaration(
-    HandlePropertyName name, DeclarationKind prevKind, TokenPos pos,
+    const ParserName* name, DeclarationKind prevKind, TokenPos pos,
     uint32_t prevPos) {
-  UniqueChars bytes = AtomToPrintableString(cx_, name);
+  UniqueChars bytes = ParserAtomToPrintableString(cx_, name);
   if (!bytes) {
     return;
   }
 
   if (prevPos == DeclaredNameInfo::npos) {
     errorAt(pos.begin, JSMSG_REDECLARED_VAR, DeclarationKindString(prevKind),
             bytes.get());
     return;
@@ -502,31 +502,31 @@ void GeneralParser<ParseHandler, Unit>::
 //
 // The 'disallowDuplicateParams' bool indicates whether the use of another
 // feature (destructuring or default arguments) disables duplicate arguments.
 // (ECMA-262 requires us to support duplicate parameter names, but, for newer
 // features, we consider the code to have "opted in" to higher standards and
 // forbid duplicates.)
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::notePositionalFormalParameter(
-    FunctionNodeType funNode, HandlePropertyName name, uint32_t beginPos,
+    FunctionNodeType funNode, const ParserName* name, uint32_t beginPos,
     bool disallowDuplicateParams, bool* duplicatedParam) {
   if (AddDeclaredNamePtr p =
           pc_->functionScope().lookupDeclaredNameForAdd(name)) {
     if (disallowDuplicateParams) {
       error(JSMSG_BAD_DUP_ARGS);
       return false;
     }
 
     // Strict-mode disallows duplicate args. We may not know whether we are
     // in strict mode or not (since the function body hasn't been parsed).
     // In such cases, report will queue up the potential error and return
     // 'true'.
     if (pc_->sc()->strict()) {
-      UniqueChars bytes = AtomToPrintableString(cx_, name);
+      UniqueChars bytes = ParserAtomToPrintableString(cx_, name);
       if (!bytes) {
         return false;
       }
       if (!strictModeError(JSMSG_DUPLICATE_FORMAL, bytes.get())) {
         return false;
       }
     }
 
@@ -551,29 +551,30 @@ bool GeneralParser<ParseHandler, Unit>::
   handler_.addFunctionFormalParameter(funNode, paramNode);
   return true;
 }
 
 template <class ParseHandler>
 bool PerHandlerParser<ParseHandler>::noteDestructuredPositionalFormalParameter(
     FunctionNodeType funNode, Node destruct) {
   // Append an empty name to the positional formals vector to keep track of
-  // argument slots when making FunctionScope::Data.
+  // argument slots when making ParserFunctionScopeData.
   if (!pc_->positionalFormalParameterNames().append(nullptr)) {
     ReportOutOfMemory(cx_);
     return false;
   }
 
   handler_.addFunctionFormalParameter(funNode, destruct);
   return true;
 }
 
 template <class ParseHandler, typename Unit>
-bool GeneralParser<ParseHandler, Unit>::noteDeclaredName(
-    HandlePropertyName name, DeclarationKind kind, TokenPos pos) {
+bool GeneralParser<ParseHandler, Unit>::noteDeclaredName(const ParserName* name,
+                                                         DeclarationKind kind,
+                                                         TokenPos pos) {
   // The asm.js validator does all its own symbol-table management so, as an
   // optimization, avoid doing any work here.
   if (pc_->useAsmOrInsideUseAsm()) {
     return true;
   }
 
   switch (kind) {
     case DeclarationKind::Var:
@@ -740,17 +741,17 @@ bool GeneralParser<ParseHandler, Unit>::
       break;
   }
 
   return true;
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::noteDeclaredPrivateName(
-    Node nameNode, HandlePropertyName name, PropertyType propType,
+    Node nameNode, const ParserName* name, PropertyType propType,
     TokenPos pos) {
   ParseContext::Scope* scope = pc_->innermostScope();
   AddDeclaredNamePtr p = scope->lookupDeclaredNameForAdd(name);
 
   PrivateNameKind kind;
   switch (propType) {
     case PropertyType::Field:
       kind = PrivateNameKind::Field;
@@ -791,17 +792,17 @@ bool GeneralParser<ParseHandler, Unit>::
     return false;
   }
   scope->lookupDeclaredName(name)->value()->setPrivateNameKind(kind);
   handler_.setPrivateNameKind(nameNode, kind);
 
   return true;
 }
 
-bool ParserBase::noteUsedNameInternal(HandlePropertyName name,
+bool ParserBase::noteUsedNameInternal(const ParserName* name,
                                       NameVisibility visibility,
                                       mozilla::Maybe<TokenPos> tokenPosition) {
   // The asm.js validator does all its own symbol-table management so, as an
   // optimization, avoid doing any work here.
   if (pc_->useAsmOrInsideUseAsm()) {
     return true;
   }
 
@@ -831,17 +832,26 @@ bool PerHandlerParser<ParseHandler>::
   if (!scope.propagateAndMarkAnnexBFunctionBoxes(pc_)) {
     return false;
   }
 
   if (handler_.canSkipLazyClosedOverBindings()) {
     // Scopes are nullptr-delimited in the BaseScript closed over bindings
     // array.
     while (JSAtom* name = handler_.nextLazyClosedOverBinding()) {
-      scope.lookupDeclaredName(name)->value()->setClosedOver();
+      // TODO-Stencil
+      //   After closed-over-bindings are snapshotted in the handler,
+      //   remove this.
+      auto mbNameId = compilationInfo_.parserAtoms.internJSAtom(cx_, name);
+      if (mbNameId.isErr()) {
+        return false;
+      }
+      const ParserName* nameId = mbNameId.unwrap()->asName();
+
+      scope.lookupDeclaredName(nameId)->value()->setClosedOver();
     }
     return true;
   }
 
   constexpr bool isSyntaxParser =
       std::is_same_v<ParseHandler, SyntaxParseHandler>;
   uint32_t scriptId = pc_->scriptId();
   uint32_t scopeId = scope.id();
@@ -887,173 +897,177 @@ bool Parser<FullParseHandler, Unit>::che
   }
   if (tt != TokenKind::Eof) {
     error(JSMSG_UNEXPECTED_TOKEN, "expression", TokenKindToDesc(tt));
     return false;
   }
   return true;
 }
 
-template <typename Scope>
-typename Scope::Data* NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc,
-                                          uint32_t numBindings) {
-  using Data = typename Scope::Data;
-  size_t allocSize = SizeOfData<typename Scope::Data>(numBindings);
+template <typename ScopeT>
+ParserScopeData<ScopeT>* NewEmptyBindingData(JSContext* cx, LifoAlloc& alloc,
+                                             uint32_t numBindings) {
+  using Data = ParserScopeData<ScopeT>;
+  size_t allocSize = SizeOfScopeData<Data>(numBindings);
   auto* bindings = alloc.newWithSize<Data>(allocSize, numBindings);
   if (!bindings) {
     ReportOutOfMemory(cx);
   }
   return bindings;
 }
 
-GlobalScope::Data* NewEmptyGlobalScopeData(JSContext* cx, LifoAlloc& alloc,
-                                           uint32_t numBindings) {
+ParserGlobalScopeData* NewEmptyGlobalScopeData(JSContext* cx, LifoAlloc& alloc,
+                                               uint32_t numBindings) {
   return NewEmptyBindingData<GlobalScope>(cx, alloc, numBindings);
 }
 
-LexicalScope::Data* NewEmptyLexicalScopeData(JSContext* cx, LifoAlloc& alloc,
-                                             uint32_t numBindings) {
+ParserLexicalScopeData* NewEmptyLexicalScopeData(JSContext* cx,
+                                                 LifoAlloc& alloc,
+                                                 uint32_t numBindings) {
   return NewEmptyBindingData<LexicalScope>(cx, alloc, numBindings);
 }
 
-FunctionScope::Data* NewEmptyFunctionScopeData(JSContext* cx, LifoAlloc& alloc,
-                                               uint32_t numBindings) {
+ParserFunctionScopeData* NewEmptyFunctionScopeData(JSContext* cx,
+                                                   LifoAlloc& alloc,
+                                                   uint32_t numBindings) {
   return NewEmptyBindingData<FunctionScope>(cx, alloc, numBindings);
 }
 
 namespace detail {
 
 template <class Data>
-static MOZ_ALWAYS_INLINE BindingName* InitializeIndexedBindings(
-    Data* data, BindingName* start, BindingName* cursor) {
+static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings(
+    Data* data, ParserBindingName* start, ParserBindingName* cursor) {
   return cursor;
 }
 
 template <class Data, typename UnsignedInteger, typename... Step>
-static MOZ_ALWAYS_INLINE BindingName* InitializeIndexedBindings(
-    Data* data, BindingName* start, BindingName* cursor,
-    UnsignedInteger Data::*field, const BindingNameVector& bindings,
+static MOZ_ALWAYS_INLINE ParserBindingName* InitializeIndexedBindings(
+    Data* data, ParserBindingName* start, ParserBindingName* cursor,
+    UnsignedInteger Data::*field, const ParserBindingNameVector& bindings,
     Step&&... step) {
   data->*field = AssertedCast<UnsignedInteger>(PointerRangeSize(start, cursor));
 
-  BindingName* newCursor =
+  ParserBindingName* newCursor =
       std::uninitialized_copy(bindings.begin(), bindings.end(), cursor);
 
   return InitializeIndexedBindings(data, start, newCursor,
                                    std::forward<Step>(step)...);
 }
 
 }  // namespace detail
 
 // Initialize |data->trailingNames| bindings, then set |data->length| to the
 // count of bindings added (which must equal |count|).
 //
 // First, |firstBindings| are added to |data->trailingNames|.  Then any "steps"
 // present are performed first to last.  Each step is 1) a pointer to a member
 // of |data| to be set to the current number of bindings added, and 2) a vector
-// of |BindingName|s to then copy into |data->trailingNames|.  (Thus each
+// of |ParserBindingName|s to then copy into |data->trailingNames|.  (Thus each
 // |data| member field indicates where the corresponding vector's names start.)
 template <class Data, typename... Step>
 static MOZ_ALWAYS_INLINE void InitializeBindingData(
-    Data* data, uint32_t count, const BindingNameVector& firstBindings,
+    Data* data, uint32_t count, const ParserBindingNameVector& firstBindings,
     Step&&... step) {
   MOZ_ASSERT(data->length == 0, "data shouldn't be filled yet");
 
-  BindingName* start = data->trailingNames.start();
-  BindingName* cursor = std::uninitialized_copy(firstBindings.begin(),
-                                                firstBindings.end(), start);
+  ParserBindingName* start = data->trailingNames.start();
+  ParserBindingName* cursor = std::uninitialized_copy(
+      firstBindings.begin(), firstBindings.end(), start);
 
 #ifdef DEBUG
-  BindingName* end =
+  ParserBindingName* end =
 #endif
       detail::InitializeIndexedBindings(data, start, cursor,
                                         std::forward<Step>(step)...);
 
   MOZ_ASSERT(PointerRangeSize(start, end) == count);
   data->length = count;
 }
 
-Maybe<GlobalScope::Data*> NewGlobalScopeData(JSContext* cx,
-                                             ParseContext::Scope& scope,
-                                             LifoAlloc& alloc,
-                                             ParseContext* pc) {
-  BindingNameVector vars(cx);
-  BindingNameVector lets(cx);
-  BindingNameVector consts(cx);
+Maybe<ParserGlobalScopeData*> NewGlobalScopeData(JSContext* cx,
+                                                 ParseContext::Scope& scope,
+                                                 LifoAlloc& alloc,
+                                                 ParseContext* pc) {
+  ParserBindingNameVector vars(cx);
+  ParserBindingNameVector lets(cx);
+  ParserBindingNameVector consts(cx);
 
   bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
   for (BindingIter bi = scope.bindings(pc); bi; bi++) {
     bool closedOver = allBindingsClosedOver || bi.closedOver();
 
     switch (bi.kind()) {
       case BindingKind::Var: {
         bool isTopLevelFunction =
             bi.declarationKind() == DeclarationKind::BodyLevelFunction;
-        BindingName binding(bi.name(), closedOver, isTopLevelFunction);
+
+        ParserBindingName binding(bi.name(), closedOver, isTopLevelFunction);
         if (!vars.append(binding)) {
           return Nothing();
         }
         break;
       }
       case BindingKind::Let: {
-        BindingName binding(bi.name(), closedOver);
+        ParserBindingName binding(bi.name(), closedOver);
         if (!lets.append(binding)) {
           return Nothing();
         }
         break;
       }
       case BindingKind::Const: {
-        BindingName binding(bi.name(), closedOver);
+        ParserBindingName binding(bi.name(), closedOver);
         if (!consts.append(binding)) {
           return Nothing();
         }
         break;
       }
       default:
         MOZ_CRASH("Bad global scope BindingKind");
     }
   }
 
-  GlobalScope::Data* bindings = nullptr;
+  ParserGlobalScopeData* bindings = nullptr;
   uint32_t numBindings = vars.length() + lets.length() + consts.length();
 
   if (numBindings > 0) {
     bindings = NewEmptyBindingData<GlobalScope>(cx, alloc, numBindings);
     if (!bindings) {
       return Nothing();
     }
 
     // The ordering here is important. See comments in GlobalScope.
     InitializeBindingData(bindings, numBindings, vars,
-                          &GlobalScope::Data::letStart, lets,
-                          &GlobalScope::Data::constStart, consts);
+                          &ParserGlobalScopeData::letStart, lets,
+                          &ParserGlobalScopeData::constStart, consts);
   }
 
   return Some(bindings);
 }
 
-Maybe<GlobalScope::Data*> ParserBase::newGlobalScopeData(
+Maybe<ParserGlobalScopeData*> ParserBase::newGlobalScopeData(
     ParseContext::Scope& scope) {
   return NewGlobalScopeData(cx_, scope, alloc_, pc_);
 }
 
-Maybe<ModuleScope::Data*> NewModuleScopeData(JSContext* cx,
-                                             ParseContext::Scope& scope,
-                                             LifoAlloc& alloc,
-                                             ParseContext* pc) {
-  BindingNameVector imports(cx);
-  BindingNameVector vars(cx);
-  BindingNameVector lets(cx);
-  BindingNameVector consts(cx);
+Maybe<ParserModuleScopeData*> NewModuleScopeData(JSContext* cx,
+                                                 ParseContext::Scope& scope,
+                                                 LifoAlloc& alloc,
+                                                 ParseContext* pc) {
+  ParserBindingNameVector imports(cx);
+  ParserBindingNameVector vars(cx);
+  ParserBindingNameVector lets(cx);
+  ParserBindingNameVector consts(cx);
 
   bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
   for (BindingIter bi = scope.bindings(pc); bi; bi++) {
     // Imports are indirect bindings and must not be given known slots.
-    BindingName binding(bi.name(), (allBindingsClosedOver || bi.closedOver()) &&
-                                       bi.kind() != BindingKind::Import);
+    ParserBindingName binding(bi.name(),
+                              (allBindingsClosedOver || bi.closedOver()) &&
+                                  bi.kind() != BindingKind::Import);
     switch (bi.kind()) {
       case BindingKind::Import:
         if (!imports.append(binding)) {
           return Nothing();
         }
         break;
       case BindingKind::Var:
         if (!vars.append(binding)) {
@@ -1070,96 +1084,98 @@ Maybe<ModuleScope::Data*> NewModuleScope
           return Nothing();
         }
         break;
       default:
         MOZ_CRASH("Bad module scope BindingKind");
     }
   }
 
-  ModuleScope::Data* bindings = nullptr;
+  ParserModuleScopeData* bindings = nullptr;
   uint32_t numBindings =
       imports.length() + vars.length() + lets.length() + consts.length();
 
   if (numBindings > 0) {
     bindings = NewEmptyBindingData<ModuleScope>(cx, alloc, numBindings);
     if (!bindings) {
       return Nothing();
     }
 
     // The ordering here is important. See comments in ModuleScope.
     InitializeBindingData(bindings, numBindings, imports,
-                          &ModuleScope::Data::varStart, vars,
-                          &ModuleScope::Data::letStart, lets,
-                          &ModuleScope::Data::constStart, consts);
+                          &ParserModuleScopeData::varStart, vars,
+                          &ParserModuleScopeData::letStart, lets,
+                          &ParserModuleScopeData::constStart, consts);
   }
 
   return Some(bindings);
 }
 
-Maybe<ModuleScope::Data*> ParserBase::newModuleScopeData(
+Maybe<ParserModuleScopeData*> ParserBase::newModuleScopeData(
     ParseContext::Scope& scope) {
   return NewModuleScopeData(cx_, scope, alloc_, pc_);
 }
 
-Maybe<EvalScope::Data*> NewEvalScopeData(JSContext* cx,
-                                         ParseContext::Scope& scope,
-                                         LifoAlloc& alloc, ParseContext* pc) {
-  BindingNameVector vars(cx);
+Maybe<ParserEvalScopeData*> NewEvalScopeData(JSContext* cx,
+                                             ParseContext::Scope& scope,
+                                             LifoAlloc& alloc,
+                                             ParseContext* pc) {
+  ParserBindingNameVector vars(cx);
 
   for (BindingIter bi = scope.bindings(pc); bi; bi++) {
     // Eval scopes only contain 'var' bindings. Make all bindings aliased
     // for now.
     MOZ_ASSERT(bi.kind() == BindingKind::Var);
     bool isTopLevelFunction =
         bi.declarationKind() == DeclarationKind::BodyLevelFunction;
-    BindingName binding(bi.name(), true, isTopLevelFunction);
+
+    ParserBindingName binding(bi.name(), true, isTopLevelFunction);
     if (!vars.append(binding)) {
       return Nothing();
     }
   }
 
-  EvalScope::Data* bindings = nullptr;
+  ParserEvalScopeData* bindings = nullptr;
   uint32_t numBindings = vars.length();
 
   if (numBindings > 0) {
     bindings = NewEmptyBindingData<EvalScope>(cx, alloc, numBindings);
     if (!bindings) {
       return Nothing();
     }
 
     InitializeBindingData(bindings, numBindings, vars);
   }
 
   return Some(bindings);
 }
 
-Maybe<EvalScope::Data*> ParserBase::newEvalScopeData(
+Maybe<ParserEvalScopeData*> ParserBase::newEvalScopeData(
     ParseContext::Scope& scope) {
   return NewEvalScopeData(cx_, scope, alloc_, pc_);
 }
 
-Maybe<FunctionScope::Data*> NewFunctionScopeData(JSContext* cx,
-                                                 ParseContext::Scope& scope,
-                                                 bool hasParameterExprs,
-                                                 LifoAlloc& alloc,
-                                                 ParseContext* pc) {
-  BindingNameVector positionalFormals(cx);
-  BindingNameVector formals(cx);
-  BindingNameVector vars(cx);
+Maybe<ParserFunctionScopeData*> NewFunctionScopeData(JSContext* cx,
+                                                     ParseContext::Scope& scope,
+                                                     bool hasParameterExprs,
+                                                     LifoAlloc& alloc,
+                                                     ParseContext* pc) {
+  ParserBindingNameVector positionalFormals(cx);
+  ParserBindingNameVector formals(cx);
+  ParserBindingNameVector vars(cx);
 
   bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
   bool hasDuplicateParams = pc->functionBox()->hasDuplicateParameters;
 
   // Positional parameter names must be added in order of appearance as they are
   // referenced using argument slots.
   for (size_t i = 0; i < pc->positionalFormalParameterNames().length(); i++) {
-    JSAtom* name = pc->positionalFormalParameterNames()[i];
-
-    BindingName bindName;
+    const ParserAtom* name = pc->positionalFormalParameterNames()[i];
+
+    ParserBindingName bindName;
     if (name) {
       DeclaredNamePtr p = scope.lookupDeclaredName(name);
 
       // Do not consider any positional formal parameters closed over if
       // there are parameter defaults. It is the binding in the defaults
       // scope that is closed over instead.
       bool closedOver =
           allBindingsClosedOver || (p && p->value()->closedOver());
@@ -1172,26 +1188,27 @@ Maybe<FunctionScope::Data*> NewFunctionS
              j > i; j--) {
           if (pc->positionalFormalParameterNames()[j] == name) {
             closedOver = false;
             break;
           }
         }
       }
 
-      bindName = BindingName(name, closedOver);
+      bindName = ParserBindingName(name, closedOver);
     }
 
     if (!positionalFormals.append(bindName)) {
       return Nothing();
     }
   }
 
   for (BindingIter bi = scope.bindings(pc); bi; bi++) {
-    BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
+    ParserBindingName binding(bi.name(),
+                              allBindingsClosedOver || bi.closedOver());
     switch (bi.kind()) {
       case BindingKind::FormalParameter:
         // Positional parameter names are already handled above.
         if (bi.declarationKind() == DeclarationKind::FormalParameter) {
           if (!formals.append(binding)) {
             return Nothing();
           }
         }
@@ -1206,30 +1223,30 @@ Maybe<FunctionScope::Data*> NewFunctionS
           return Nothing();
         }
         break;
       default:
         break;
     }
   }
 
-  FunctionScope::Data* bindings = nullptr;
+  ParserFunctionScopeData* bindings = nullptr;
   uint32_t numBindings =
       positionalFormals.length() + formals.length() + vars.length();
 
   if (numBindings > 0) {
     bindings = NewEmptyBindingData<FunctionScope>(cx, alloc, numBindings);
     if (!bindings) {
       return Nothing();
     }
 
     // The ordering here is important. See comments in FunctionScope.
     InitializeBindingData(bindings, numBindings, positionalFormals,
-                          &FunctionScope::Data::nonPositionalFormalStart,
-                          formals, &FunctionScope::Data::varStart, vars);
+                          &ParserFunctionScopeData::nonPositionalFormalStart,
+                          formals, &ParserFunctionScopeData::varStart, vars);
   }
 
   return Some(bindings);
 }
 
 // Compute if `NewFunctionScopeData` would return any binding list with any
 // entry marked as closed-over. This is done without the need to allocate the
 // binding list. If true, an EnvironmentObject will be needed at runtime.
@@ -1248,43 +1265,44 @@ bool FunctionScopeHasClosedOverBindings(
       default:
         break;
     }
   }
 
   return false;
 }
 
-Maybe<FunctionScope::Data*> ParserBase::newFunctionScopeData(
+Maybe<ParserFunctionScopeData*> ParserBase::newFunctionScopeData(
     ParseContext::Scope& scope, bool hasParameterExprs) {
   return NewFunctionScopeData(cx_, scope, hasParameterExprs, alloc_, pc_);
 }
 
-VarScope::Data* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc,
-                                     uint32_t numBindings) {
+ParserVarScopeData* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc,
+                                         uint32_t numBindings) {
   return NewEmptyBindingData<VarScope>(cx, alloc, numBindings);
 }
 
-Maybe<VarScope::Data*> NewVarScopeData(JSContext* cx,
-                                       ParseContext::Scope& scope,
-                                       LifoAlloc& alloc, ParseContext* pc) {
-  BindingNameVector vars(cx);
+Maybe<ParserVarScopeData*> NewVarScopeData(JSContext* cx,
+                                           ParseContext::Scope& scope,
+                                           LifoAlloc& alloc, ParseContext* pc) {
+  ParserBindingNameVector vars(cx);
 
   bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
 
   for (BindingIter bi = scope.bindings(pc); bi; bi++) {
     if (bi.kind() == BindingKind::Var) {
-      BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
+      ParserBindingName binding(bi.name(),
+                                allBindingsClosedOver || bi.closedOver());
       if (!vars.append(binding)) {
         return Nothing();
       }
     }
   }
 
-  VarScope::Data* bindings = nullptr;
+  ParserVarScopeData* bindings = nullptr;
   uint32_t numBindings = vars.length();
 
   if (numBindings > 0) {
     bindings = NewEmptyBindingData<VarScope>(cx, alloc, numBindings);
     if (!bindings) {
       return Nothing();
     }
 
@@ -1301,59 +1319,61 @@ static bool VarScopeHasBindings(ParseCon
     if (bi.kind() == BindingKind::Var) {
       return true;
     }
   }
 
   return false;
 }
 
-Maybe<VarScope::Data*> ParserBase::newVarScopeData(ParseContext::Scope& scope) {
+Maybe<ParserVarScopeData*> ParserBase::newVarScopeData(
+    ParseContext::Scope& scope) {
   return NewVarScopeData(cx_, scope, alloc_, pc_);
 }
 
-Maybe<LexicalScope::Data*> NewLexicalScopeData(JSContext* cx,
-                                               ParseContext::Scope& scope,
-                                               LifoAlloc& alloc,
-                                               ParseContext* pc) {
-  BindingNameVector lets(cx);
-  BindingNameVector consts(cx);
+Maybe<ParserLexicalScopeData*> NewLexicalScopeData(JSContext* cx,
+                                                   ParseContext::Scope& scope,
+                                                   LifoAlloc& alloc,
+                                                   ParseContext* pc) {
+  ParserBindingNameVector lets(cx);
+  ParserBindingNameVector consts(cx);
 
   bool allBindingsClosedOver = pc->sc()->allBindingsClosedOver();
 
   for (BindingIter bi = scope.bindings(pc); bi; bi++) {
-    BindingName binding(bi.name(), allBindingsClosedOver || bi.closedOver());
+    ParserBindingName binding(bi.name(),
+                              allBindingsClosedOver || bi.closedOver());
     switch (bi.kind()) {
       case BindingKind::Let:
         if (!lets.append(binding)) {
           return Nothing();
         }
         break;
       case BindingKind::Const:
         if (!consts.append(binding)) {
           return Nothing();
         }
         break;
       default:
         break;
     }
   }
 
-  LexicalScope::Data* bindings = nullptr;
+  ParserLexicalScopeData* bindings = nullptr;
   uint32_t numBindings = lets.length() + consts.length();
 
   if (numBindings > 0) {
     bindings = NewEmptyBindingData<LexicalScope>(cx, alloc, numBindings);
     if (!bindings) {
       return Nothing();
     }
 
     // The ordering here is important. See comments in LexicalScope.
     InitializeBindingData(bindings, numBindings, lets,
-                          &LexicalScope::Data::constStart, consts);
+                          &ParserLexicalScopeData::constStart, consts);
   }
 
   return Some(bindings);
 }
 
 // Compute if `NewLexicalScopeData` would return any binding list with any entry
 // marked as closed-over. This is done without the need to allocate the binding
 // list. If true, an EnvironmentObject will be needed at runtime.
@@ -1373,17 +1393,17 @@ bool LexicalScopeHasClosedOverBindings(P
       default:
         break;
     }
   }
 
   return false;
 }
 
-Maybe<LexicalScope::Data*> ParserBase::newLexicalScopeData(
+Maybe<ParserLexicalScopeData*> ParserBase::newLexicalScopeData(
     ParseContext::Scope& scope) {
   return NewLexicalScopeData(cx_, scope, alloc_, pc_);
 }
 
 template <>
 SyntaxParseHandler::LexicalScopeNodeType
 PerHandlerParser<SyntaxParseHandler>::finishLexicalScope(
     ParseContext::Scope& scope, Node body, ScopeKind kind) {
@@ -1396,17 +1416,17 @@ PerHandlerParser<SyntaxParseHandler>::fi
 
 template <>
 LexicalScopeNode* PerHandlerParser<FullParseHandler>::finishLexicalScope(
     ParseContext::Scope& scope, ParseNode* body, ScopeKind kind) {
   if (!propagateFreeNamesAndMarkClosedOverBindings(scope)) {
     return nullptr;
   }
 
-  Maybe<LexicalScope::Data*> bindings = newLexicalScopeData(scope);
+  Maybe<ParserLexicalScopeData*> bindings = newLexicalScopeData(scope);
   if (!bindings) {
     return nullptr;
   }
 
   return handler_.newLexicalScope(*bindings, body, kind);
 }
 
 template <class ParseHandler>
@@ -1430,17 +1450,17 @@ bool PerHandlerParser<ParseHandler>::che
   }
 
   // It is an early error if there's private name references unbound,
   // unless it's an eval, in which case we need to check the scope
   // chain.
   if (!evalSc) {
     // The unbound private names are sorted, so just grab the first one.
     UnboundPrivateName minimum = unboundPrivateNames[0];
-    UniqueChars str = AtomToPrintableString(cx_, minimum.atom);
+    UniqueChars str = ParserAtomToPrintableString(cx_, minimum.atom);
     if (!str) {
       return false;
     }
 
     errorAt(minimum.position.begin, JSMSG_MISSING_PRIVATE_DECL, str.get());
     return false;
   }
 
@@ -1453,25 +1473,25 @@ bool PerHandlerParser<ParseHandler>::che
     for (ScopeIter si(enclosingScope); si; si++) {
       // Private names are only found within class body scopes.
       if (si.scope()->kind() != ScopeKind::ClassBody) {
         continue;
       }
 
       // Look for a matching binding.
       for (js::BindingIter bi(si.scope()); bi; bi++) {
-        if (bi.name() == unboundName.atom) {
+        if (unboundName.atom->equalsJSAtom(bi.name())) {
           // Awesome. We found it, we're done here!
           return true;
         }
       }
     }
 
     // Didn't find a matching binding, so issue an error.
-    UniqueChars str = AtomToPrintableString(cx, unboundName.atom);
+    UniqueChars str = ParserAtomToPrintableString(cx, unboundName.atom);
     if (!str) {
       return false;
     }
     parser->errorAt(unboundName.position.begin, JSMSG_MISSING_PRIVATE_DECL,
                     str.get());
     return false;
   };
 
@@ -1573,17 +1593,17 @@ LexicalScopeNode* Parser<FullParseHandle
 
   // For eval scripts, since all bindings are automatically considered
   // closed over, we don't need to call propagateFreeNamesAndMarkClosed-
   // OverBindings. However, Annex B.3.3 functions still need to be marked.
   if (!varScope.propagateAndMarkAnnexBFunctionBoxes(pc_)) {
     return nullptr;
   }
 
-  Maybe<EvalScope::Data*> bindings = newEvalScopeData(pc_->varScope());
+  Maybe<ParserEvalScopeData*> bindings = newEvalScopeData(pc_->varScope());
   if (!bindings) {
     return nullptr;
   }
   evalsc->bindings = *bindings;
 
   return body;
 }
 
@@ -1633,17 +1653,17 @@ ListNode* Parser<FullParseHandler, Unit>
 
   // For global scripts, whether bindings are closed over or not doesn't
   // matter, so no need to call propagateFreeNamesAndMarkClosedOver-
   // Bindings. However, Annex B.3.3 functions still need to be marked.
   if (!varScope.propagateAndMarkAnnexBFunctionBoxes(pc_)) {
     return nullptr;
   }
 
-  Maybe<GlobalScope::Data*> bindings = newGlobalScopeData(pc_->varScope());
+  Maybe<ParserGlobalScopeData*> bindings = newGlobalScopeData(pc_->varScope());
   if (!bindings) {
     return nullptr;
   }
   globalsc->bindings = *bindings;
 
   return body;
 }
 
@@ -1691,41 +1711,41 @@ ModuleNode* Parser<FullParseHandler, Uni
           this->compilationInfo_.moduleMetadata.get())) {
     return null();
   }
 
   // Check exported local bindings exist and mark them as closed over.
   StencilModuleMetadata& moduleMetadata =
       this->compilationInfo_.moduleMetadata.get();
   for (auto entry : moduleMetadata.localExportEntries) {
-    JSAtom* name = entry.localName;
-    MOZ_ASSERT(name);
-
-    DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(name);
+    const ParserAtom* nameId = entry.localName;
+    MOZ_ASSERT(nameId);
+
+    DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(nameId);
     if (!p) {
-      UniqueChars str = AtomToPrintableString(cx_, name);
+      UniqueChars str = ParserAtomToPrintableString(cx_, nameId);
       if (!str) {
         return null();
       }
 
       errorNoOffset(JSMSG_MISSING_EXPORT, str.get());
       return null();
     }
 
     p->value()->setClosedOver();
   }
 
   // Reserve an environment slot for a "*namespace*" psuedo-binding and mark as
   // closed-over. We do not know until module linking if this will be used.
-  if (!noteDeclaredName(cx_->names().starNamespaceStar, DeclarationKind::Const,
-                        pos())) {
+  if (!noteDeclaredName(cx_->parserNames().starNamespaceStar,
+                        DeclarationKind::Const, pos())) {
     return nullptr;
   }
   modulepc.varScope()
-      .lookupDeclaredName(cx_->names().starNamespaceStar)
+      .lookupDeclaredName(cx_->parserNames().starNamespaceStar)
       ->value()
       ->setClosedOver();
 
   if (!CheckParseTree(cx_, alloc_, stmtList)) {
     return null();
   }
 
   ParseNode* node = stmtList;
@@ -1746,17 +1766,18 @@ ModuleNode* Parser<FullParseHandler, Uni
   if (!checkForUndefinedPrivateFields()) {
     return null();
   }
 
   if (!propagateFreeNamesAndMarkClosedOverBindings(modulepc.varScope())) {
     return null();
   }
 
-  Maybe<ModuleScope::Data*> bindings = newModuleScopeData(modulepc.varScope());
+  Maybe<ParserModuleScopeData*> bindings =
+      newModuleScopeData(modulepc.varScope());
   if (!bindings) {
     return nullptr;
   }
 
   modulesc->bindings = *bindings;
   return moduleNode;
 }
 
@@ -1764,17 +1785,17 @@ template <typename Unit>
 SyntaxParseHandler::ModuleNodeType Parser<SyntaxParseHandler, Unit>::moduleBody(
     ModuleSharedContext* modulesc) {
   MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
   return SyntaxParseHandler::NodeFailure;
 }
 
 template <class ParseHandler>
 typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::newInternalDotName(HandlePropertyName name) {
+PerHandlerParser<ParseHandler>::newInternalDotName(const ParserName* name) {
   NameNodeType nameNode = newName(name);
   if (!nameNode) {
     return null();
   }
   if (!noteUsedName(name)) {
     return null();
   }
   return nameNode;
@@ -1846,43 +1867,43 @@ bool PerHandlerParser<FullParseHandler>:
   if (funbox->isInterpreted()) {
     // BCE will need to generate bytecode for this.
     funbox->emitBytecode = true;
   }
 
   bool hasParameterExprs = funbox->hasParameterExprs;
 
   if (hasParameterExprs) {
-    Maybe<VarScope::Data*> bindings = newVarScopeData(pc_->varScope());
+    Maybe<ParserVarScopeData*> bindings = newVarScopeData(pc_->varScope());
     if (!bindings) {
       return false;
     }
-    funbox->extraVarScopeBindings().set(*bindings);
+    funbox->setExtraVarScopeBindings(*bindings);
 
     MOZ_ASSERT(bool(*bindings) == VarScopeHasBindings(pc_));
     MOZ_ASSERT_IF(!funbox->needsExtraBodyVarEnvironmentRegardlessOfBindings(),
                   bool(*bindings) == funbox->functionHasExtraBodyVarScope());
   }
 
   {
-    Maybe<FunctionScope::Data*> bindings =
+    Maybe<ParserFunctionScopeData*> bindings =
         newFunctionScopeData(pc_->functionScope(), hasParameterExprs);
     if (!bindings) {
       return false;
     }
-    funbox->functionScopeBindings().set(*bindings);
+    funbox->setFunctionScopeBindings(*bindings);
   }
 
   if (funbox->isNamedLambda() && !isStandaloneFunction) {
-    Maybe<LexicalScope::Data*> bindings =
+    Maybe<ParserLexicalScopeData*> bindings =
         newLexicalScopeData(pc_->namedLambdaScope());
     if (!bindings) {
       return false;
     }
-    funbox->namedLambdaBindings().set(*bindings);
+    funbox->setNamedLambdaBindings(*bindings);
   }
 
   funbox->finishScriptFlags();
   funbox->copyFunctionFields(stencil);
 
   return true;
 }
 
@@ -1903,19 +1924,19 @@ bool PerHandlerParser<SyntaxParseHandler
 
   funbox->finishScriptFlags();
   funbox->copyFunctionFields(stencil);
   funbox->copyScriptFields(stencil);
 
   // Elide nullptr sentinels from end of binding list. These are inserted for
   // each scope regardless of if any bindings are actually closed over.
   {
-    AtomVector& COB = pc_->closedOverBindingsForLazy();
-    while (!COB.empty() && (COB.back() == nullptr)) {
-      COB.popBack();
+    AtomVector& closedOver = pc_->closedOverBindingsForLazy();
+    while (!closedOver.empty() && !closedOver.back()) {
+      closedOver.popBack();
     }
   }
 
   // Check if we will overflow the `ngcthings` field later.
   mozilla::CheckedUint32 ngcthings =
       mozilla::CheckedUint32(pc_->innerFunctionIndexesForLazy.length()) +
       mozilla::CheckedUint32(pc_->closedOverBindingsForLazy().length());
   if (!ngcthings.isValid()) {
@@ -2051,17 +2072,17 @@ FunctionNode* Parser<FullParseHandler, U
   if (generatorKind == GeneratorKind::Generator) {
     MOZ_ASSERT(tt == TokenKind::Mul);
     if (!tokenStream.getToken(&tt)) {
       return null();
     }
   }
 
   // Skip function name, if present.
-  RootedAtom explicitName(cx_);
+  const ParserAtom* explicitName = nullptr;
   if (TokenKindIsPossibleIdentifierName(tt)) {
     explicitName = anyChars.currentName();
   } else {
     anyChars.ungetToken();
   }
 
   FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
   if (!funNode) {
@@ -2308,33 +2329,30 @@ bool ParserBase::leaveInnerFunction(Pars
     return false;
   }
 
   PropagateTransitiveParseFlags(pc_->functionBox(), outerpc->sc());
 
   return true;
 }
 
-JSAtom* ParserBase::prefixAccessorName(PropertyType propType,
-                                       HandleAtom propAtom) {
-  RootedAtom prefix(cx_);
+const ParserAtom* ParserBase::prefixAccessorName(PropertyType propType,
+                                                 const ParserAtom* propAtom) {
+  const ParserAtom* prefix = nullptr;
   if (propType == PropertyType::Setter) {
     prefix = cx_->parserNames().setPrefix;
   } else {
     MOZ_ASSERT(propType == PropertyType::Getter);
     prefix = cx_->parserNames().getPrefix;
   }
 
-  RootedString str(
-      cx_, ConcatStrings<CanGC>(cx_, prefix, propAtom, js::gc::TenuredHeap));
-  if (!str) {
-    return nullptr;
-  }
-
-  return AtomizeString(cx_, str);
+  const ParserAtom* atoms[2] = {prefix, propAtom};
+  auto atomsRange = mozilla::Range(atoms, 2);
+  return compilationInfo_.parserAtoms.concatAtoms(cx_, atomsRange)
+      .unwrapOr(nullptr);
 }
 
 template <class ParseHandler, typename Unit>
 void GeneralParser<ParseHandler, Unit>::setFunctionStartAtCurrentToken(
     FunctionBox* funbox) const {
   uint32_t bufStart = anyChars.currentToken().pos.begin;
 
   uint32_t startLine, startColumn;
@@ -2522,17 +2540,17 @@ bool GeneralParser<ParseHandler, Unit>::
             error(JSMSG_MISSING_FORMAL);
             return false;
           }
 
           if (parenFreeArrow) {
             setFunctionStartAtCurrentToken(funbox);
           }
 
-          RootedPropertyName name(cx_, bindingIdentifier(yieldHandling));
+          const ParserName* name = bindingIdentifier(yieldHandling);
           if (!name) {
             return false;
           }
 
           if (!notePositionalFormalParameter(funNode, name, pos().begin,
                                              disallowDuplicateParams,
                                              &duplicatedParam)) {
             return false;
@@ -2658,18 +2676,29 @@ bool Parser<FullParseHandler, Unit>::ski
     FunctionNode* funNode, uint32_t toStringStart, FunctionSyntaxKind kind,
     bool tryAnnexB) {
   // When a lazily-parsed function is called, we only fully parse (and emit)
   // that function, not any of its nested children. The initial syntax-only
   // parse recorded the free variables of nested functions and their extents,
   // so we can skip over them after accounting for their free variables.
 
   RootedFunction fun(cx_, handler_.nextLazyInnerFunction());
+
+  // TODO-Stencil: Consider for snapshotting.
+  const ParserAtom* displayAtom = nullptr;
+  if (fun->displayAtom()) {
+    displayAtom =
+        this->compilationInfo_.lowerJSAtomToParserAtom(fun->displayAtom());
+    if (!displayAtom) {
+      return false;
+    }
+  }
+
   FunctionBox* funbox =
-      newFunctionBox(funNode, fun->displayAtom(), fun->flags(), toStringStart,
+      newFunctionBox(funNode, displayAtom, fun->flags(), toStringStart,
                      Directives(/* strict = */ false), fun->generatorKind(),
                      fun->asyncKind(), TopLevelFunction::No);
   if (!funbox) {
     return false;
   }
 
   ScriptStencil& stencil = funbox->functionStencil().get();
   funbox->initFromLazyFunction(fun);
@@ -2794,19 +2823,19 @@ GeneralParser<ParseHandler, Unit>::templ
   } while (tt == TokenKind::TemplateHead);
   return nodeList;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::functionDefinition(
     FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling,
-    YieldHandling yieldHandling, HandleAtom funName, FunctionSyntaxKind kind,
-    GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
-    bool tryAnnexB /* = false */) {
+    YieldHandling yieldHandling, const ParserAtom* funName,
+    FunctionSyntaxKind kind, GeneratorKind generatorKind,
+    FunctionAsyncKind asyncKind, bool tryAnnexB /* = false */) {
   MOZ_ASSERT_IF(kind == FunctionSyntaxKind::Statement, funName);
 
   // If we see any inner function, note it on our current context. The bytecode
   // emitter may eliminate the function later, but we use a conservative
   // definition for consistency between lazy and full parsing.
   pc_->sc()->setHasInnerFunctions();
 
   // When fully parsing a lazy script, we do not fully reparse its inner
@@ -2887,17 +2916,17 @@ bool Parser<FullParseHandler, Unit>::adv
 
   anyChars.adoptState(syntaxParser->anyChars);
   tokenStream.adoptState(syntaxParser->tokenStream);
   return true;
 }
 
 template <typename Unit>
 bool Parser<FullParseHandler, Unit>::trySyntaxParseInnerFunction(
-    FunctionNode** funNode, HandleAtom explicitName, FunctionFlags flags,
+    FunctionNode** funNode, const ParserAtom* explicitName, FunctionFlags flags,
     uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling,
     FunctionSyntaxKind kind, GeneratorKind generatorKind,
     FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives,
     Directives* newDirectives) {
   // Try a syntax parse for this inner function.
   do {
     // If we're assuming this function is an IIFE, always perform a full
     // parse to avoid the overhead of a lazy syntax-only parse. Although
@@ -2990,42 +3019,42 @@ bool Parser<FullParseHandler, Unit>::try
   }
 
   *funNode = innerFunc;
   return true;
 }
 
 template <typename Unit>
 bool Parser<SyntaxParseHandler, Unit>::trySyntaxParseInnerFunction(
-    FunctionNodeType* funNode, HandleAtom explicitName, FunctionFlags flags,
-    uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling,
-    FunctionSyntaxKind kind, GeneratorKind generatorKind,
-    FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives,
-    Directives* newDirectives) {
+    FunctionNodeType* funNode, const ParserAtom* explicitName,
+    FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+    YieldHandling yieldHandling, FunctionSyntaxKind kind,
+    GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+    Directives inheritedDirectives, Directives* newDirectives) {
   // This is already a syntax parser, so just parse the inner function.
   FunctionNodeType innerFunc =
       innerFunction(*funNode, pc_, explicitName, flags, toStringStart,
                     inHandling, yieldHandling, kind, generatorKind, asyncKind,
                     tryAnnexB, inheritedDirectives, newDirectives);
 
   if (!innerFunc) {
     return false;
   }
 
   *funNode = innerFunc;
   return true;
 }
 
 template <class ParseHandler, typename Unit>
 inline bool GeneralParser<ParseHandler, Unit>::trySyntaxParseInnerFunction(
-    FunctionNodeType* funNode, HandleAtom explicitName, FunctionFlags flags,
-    uint32_t toStringStart, InHandling inHandling, YieldHandling yieldHandling,
-    FunctionSyntaxKind kind, GeneratorKind generatorKind,
-    FunctionAsyncKind asyncKind, bool tryAnnexB, Directives inheritedDirectives,
-    Directives* newDirectives) {
+    FunctionNodeType* funNode, const ParserAtom* explicitName,
+    FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+    YieldHandling yieldHandling, FunctionSyntaxKind kind,
+    GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
+    Directives inheritedDirectives, Directives* newDirectives) {
   return asFinalParser()->trySyntaxParseInnerFunction(
       funNode, explicitName, flags, toStringStart, inHandling, yieldHandling,
       kind, generatorKind, asyncKind, tryAnnexB, inheritedDirectives,
       newDirectives);
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
@@ -3054,19 +3083,19 @@ GeneralParser<ParseHandler, Unit>::inner
   }
 
   return funNode;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::innerFunction(
-    FunctionNodeType funNode, ParseContext* outerpc, HandleAtom explicitName,
-    FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
-    YieldHandling yieldHandling, FunctionSyntaxKind kind,
+    FunctionNodeType funNode, ParseContext* outerpc,
+    const ParserAtom* explicitName, FunctionFlags flags, uint32_t toStringStart,
+    InHandling inHandling, YieldHandling yieldHandling, FunctionSyntaxKind kind,
     GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
     Directives inheritedDirectives, Directives* newDirectives) {
   // Note that it is possible for outerpc != this->pc_, as we may be
   // attempting to syntax parse an inner function from an outer full
   // parser. In that case, outerpc is a SourceParseContext from the full parser
   // instead of the current top of the stack of the syntax parser.
 
   FunctionBox* funbox = newFunctionBox(
@@ -3096,17 +3125,17 @@ GeneralParser<ParseHandler, Unit>::inner
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::appendToCallSiteObj(
     CallSiteNodeType callSiteObj) {
   Node cookedNode = noSubstitutionTaggedTemplate();
   if (!cookedNode) {
     return false;
   }
 
-  JSAtom* atom = tokenStream.getRawTemplateStringAtom();
+  const ParserAtom* atom = tokenStream.getRawTemplateStringAtom();
   if (!atom) {
     return false;
   }
   NameNodeType rawNode = handler_.newTemplateStringLiteral(atom, pos());
   if (!rawNode) {
     return false;
   }
 
@@ -3141,20 +3170,30 @@ FunctionNode* Parser<FullParseHandler, U
     syntaxKind = FunctionSyntaxKind::Arrow;
   }
 
   FunctionNodeType funNode = handler_.newFunction(syntaxKind, pos());
   if (!funNode) {
     return null();
   }
 
+  // TODO-Stencil: Consider for snapshotting.
+  const ParserAtom* displayAtom = nullptr;
+  if (fun->displayAtom()) {
+    displayAtom =
+        this->compilationInfo_.lowerJSAtomToParserAtom(fun->displayAtom());
+    if (!displayAtom) {
+      return null();
+    }
+  }
+
   Directives directives(strict);
-  FunctionBox* funbox = newFunctionBox(
-      funNode, fun->displayAtom(), fun->flags(), toStringStart, directives,
-      generatorKind, asyncKind, TopLevelFunction::Yes);
+  FunctionBox* funbox = newFunctionBox(funNode, displayAtom, fun->flags(),
+                                       toStringStart, directives, generatorKind,
+                                       asyncKind, TopLevelFunction::Yes);
   if (!funbox) {
     return null();
   }
   funbox->initFromLazyFunction(fun);
   funbox->initStandalone(this->getCompilationInfo().scopeContext, fun->flags(),
                          syntaxKind);
   if (fun->isClassConstructor()) {
     funbox->setMemberInitializers(fun->baseScript()->getMemberInitializers());
@@ -3315,17 +3354,17 @@ bool GeneralParser<ParseHandler, Unit>::
   // Revalidate the function name when we transitioned to strict mode.
   if ((kind == FunctionSyntaxKind::Statement ||
        kind == FunctionSyntaxKind::Expression) &&
       funbox->explicitName() && !inheritedStrict && pc_->sc()->strict()) {
     MOZ_ASSERT(pc_->sc()->hasExplicitUseStrict(),
                "strict mode should only change when a 'use strict' directive "
                "is present");
 
-    PropertyName* propertyName = funbox->explicitName()->asPropertyName();
+    const ParserName* propertyName = funbox->explicitName()->asName();
     YieldHandling nameYieldHandling;
     if (kind == FunctionSyntaxKind::Expression) {
       // Named lambda has binding inside it.
       nameYieldHandling = bodyYieldHandling;
     } else {
       // Otherwise YieldHandling cannot be checked at this point
       // because of different context.
       // It should already be checked before this point.
@@ -3422,17 +3461,17 @@ GeneralParser<ParseHandler, Unit>::funct
   GeneratorKind generatorKind = GeneratorKind::NotGenerator;
   if (tt == TokenKind::Mul) {
     generatorKind = GeneratorKind::Generator;
     if (!tokenStream.getToken(&tt)) {
       return null();
     }
   }
 
-  RootedPropertyName name(cx_);
+  const ParserName* name = nullptr;
   if (TokenKindIsPossibleIdentifier(tt)) {
     name = bindingIdentifier(yieldHandling);
     if (!name) {
       return null();
     }
   } else if (defaultHandling == AllowDefaultName) {
     name = cx_->parserNames().default_;
     anyChars.ungetToken();
@@ -3502,17 +3541,17 @@ GeneralParser<ParseHandler, Unit>::funct
     generatorKind = GeneratorKind::Generator;
     if (!tokenStream.getToken(&tt)) {
       return null();
     }
   }
 
   YieldHandling yieldHandling = GetYieldHandling(generatorKind);
 
-  RootedPropertyName name(cx_);
+  const ParserName* name = nullptr;
   if (TokenKindIsPossibleIdentifier(tt)) {
     name = bindingIdentifier(yieldHandling);
     if (!name) {
       return null();
     }
   } else {
     anyChars.ungetToken();
   }
@@ -3533,23 +3572,24 @@ GeneralParser<ParseHandler, Unit>::funct
 
 /*
  * Return true if this node, known to be an unparenthesized string literal,
  * could be the string of a directive in a Directive Prologue. Directive
  * strings never contain escape sequences or line continuations.
  * isEscapeFreeStringLiteral, below, checks whether the node itself could be
  * a directive.
  */
-static inline bool IsEscapeFreeStringLiteral(const TokenPos& pos, JSAtom* str) {
+static inline bool IsEscapeFreeStringLiteral(const TokenPos& pos,
+                                             const ParserAtom* atom) {
   /*
    * If the string's length in the source code is its length as a value,
    * accounting for the quotes, then it must not contain any escape
    * sequences or line continuations.
    */
-  return pos.begin + str->length() + 2 == pos.end;
+  return pos.begin + atom->length() + 2 == pos.end;
 }
 
 template <typename Unit>
 bool Parser<SyntaxParseHandler, Unit>::asmJS(ListNodeType list) {
   // While asm.js could technically be validated and compiled during syntax
   // parsing, we have no guarantee that some later JS wouldn't abort the
   // syntax parse and cause us to re-parse (and re-compile) the asm.js module.
   // For simplicity, unconditionally abort the syntax parse when "use asm" is
@@ -3589,17 +3629,17 @@ bool Parser<FullParseHandler, Unit>::asm
   pc_->functionBox()->useAsm = true;
 
   // Attempt to validate and compile this asm.js module. On success, the
   // tokenStream has been advanced to the closing }. On failure, the
   // tokenStream is in an indeterminate state and we must reparse the
   // function from the beginning. Reparsing is triggered by marking that a
   // new directive has been encountered and returning 'false'.
   bool validated;
-  if (!CompileAsmJS(cx_, *this, list, &validated)) {
+  if (!CompileAsmJS(cx_, this->compilationInfo_, *this, list, &validated)) {
     return false;
   }
   if (!validated) {
     pc_->newDirectives->setAsmJS();
     return false;
   }
 
   return true;
@@ -3628,17 +3668,17 @@ inline bool GeneralParser<ParseHandler, 
  * That is, even though "use\x20loose" can never be a directive, now or in the
  * future (because of the hex escape), the Directive Prologue extends through it
  * to the "use strict" statement, which is indeed a directive.
  */
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::maybeParseDirective(
     ListNodeType list, Node possibleDirective, bool* cont) {
   TokenPos directivePos;
-  JSAtom* directive =
+  const ParserAtom* directive =
       handler_.isStringExprStatement(possibleDirective, &directivePos);
 
   *cont = !!directive;
   if (!*cont) {
     return true;
   }
 
   if (IsEscapeFreeStringLiteral(directivePos, directive)) {
@@ -3778,31 +3818,32 @@ typename ParseHandler::Node GeneralParse
     return null();
   }
 
   return pn;
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::matchLabel(
-    YieldHandling yieldHandling, MutableHandle<PropertyName*> label) {
+    YieldHandling yieldHandling, const ParserName** labelOut) {
+  MOZ_ASSERT(labelOut != nullptr);
   TokenKind tt = TokenKind::Eof;
   if (!tokenStream.peekTokenSameLine(&tt, TokenStream::SlashIsRegExp)) {
     return false;
   }
 
   if (TokenKindIsPossibleIdentifier(tt)) {
     tokenStream.consumeKnownToken(tt, TokenStream::SlashIsRegExp);
 
-    label.set(labelIdentifier(yieldHandling));
-    if (!label) {
+    *labelOut = labelIdentifier(yieldHandling);
+    if (!*labelOut) {
       return false;
     }
   } else {
-    label.set(nullptr);
+    *labelOut = nullptr;
   }
   return true;
 }
 
 template <class ParseHandler, typename Unit>
 GeneralParser<ParseHandler, Unit>::PossibleError::PossibleError(
     GeneralParser<ParseHandler, Unit>& parser)
     : parser_(parser) {}
@@ -3956,17 +3997,17 @@ GeneralParser<ParseHandler, Unit>::bindi
 
   return assign;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::NameNodeType
 GeneralParser<ParseHandler, Unit>::bindingIdentifier(
     DeclarationKind kind, YieldHandling yieldHandling) {
-  RootedPropertyName name(cx_, bindingIdentifier(yieldHandling));
+  const ParserName* name = bindingIdentifier(yieldHandling);
   if (!name) {
     return null();
   }
 
   NameNodeType binding = newName(name);
   if (!binding || !noteDeclaredName(name, kind, pos())) {
     return null();
   }
@@ -4006,17 +4047,17 @@ GeneralParser<ParseHandler, Unit>::objec
 
   uint32_t begin = pos().begin;
   ListNodeType literal = handler_.newObjectLiteral(begin);
   if (!literal) {
     return null();
   }
 
   Maybe<DeclarationKind> declKind = Some(kind);
-  RootedAtom propAtom(cx_);
+  const ParserAtom* propAtom = nullptr;
   for (;;) {
     TokenKind tt;
     if (!tokenStream.peekToken(&tt)) {
       return null();
     }
     if (tt == TokenKind::RightCurly) {
       break;
     }
@@ -4445,17 +4486,17 @@ typename ParseHandler::Node GeneralParse
     YieldHandling yieldHandling, ParseNodeKind* forHeadKind,
     Node* forInOrOfExpression) {
   // Anything other than possible identifier is an error.
   if (!TokenKindIsPossibleIdentifier(tt)) {
     error(JSMSG_NO_VARIABLE_NAME);
     return null();
   }
 
-  RootedPropertyName name(cx_, bindingIdentifier(yieldHandling));
+  const ParserName* name = bindingIdentifier(yieldHandling);
   if (!name) {
     return null();
   }
 
   NameNodeType binding = newName(name);
   if (!binding) {
     return null();
   }
@@ -4639,17 +4680,17 @@ bool Parser<FullParseHandler, Unit>::nam
         break;
       }
 
       if (!TokenKindIsPossibleIdentifierName(tt)) {
         error(JSMSG_NO_IMPORT_NAME);
         return false;
       }
 
-      Rooted<PropertyName*> importName(cx_, anyChars.currentName());
+      const ParserName* importName = anyChars.currentName();
       TokenPos importNamePos = pos();
 
       bool matched;
       if (!tokenStream.matchToken(&matched, TokenKind::As)) {
         return false;
       }
 
       if (matched) {
@@ -4668,17 +4709,17 @@ bool Parser<FullParseHandler, Unit>::nam
         // by the keyword 'as'.
         // See the ImportSpecifier production in ES6 section 15.2.2.
         if (IsKeyword(importName)) {
           error(JSMSG_AS_AFTER_RESERVED_WORD, ReservedWordToCharZ(importName));
           return false;
         }
       }
 
-      RootedPropertyName bindingAtom(cx_, importedBinding());
+      const ParserName* bindingAtom = importedBinding();
       if (!bindingAtom) {
         return false;
       }
 
       NameNodeType bindingName = newName(bindingAtom);
       if (!bindingName) {
         return false;
       }
@@ -4729,17 +4770,17 @@ bool Parser<FullParseHandler, Unit>::nam
     if (!importName) {
       return false;
     }
 
     // Namespace imports are are not indirect bindings but lexical
     // definitions that hold a module namespace object. They are treated
     // as const variables which are initialized during the
     // ModuleInstantiate step.
-    RootedPropertyName bindingName(cx_, importedBinding());
+    const ParserName* bindingName = importedBinding();
     if (!bindingName) {
       return false;
     }
     NameNodeType bindingNameNode = newName(bindingName);
     if (!bindingNameNode) {
       return false;
     }
     if (!noteDeclaredName(bindingName, DeclarationKind::Const, pos())) {
@@ -4797,17 +4838,17 @@ BinaryNode* Parser<FullParseHandler, Uni
       // specifier to the list, with 'default' as the import name and
       // 'a' as the binding name. This is equivalent to
       // |import { default as a } from 'b'|.
       NameNodeType importName = newName(cx_->parserNames().default_);
       if (!importName) {
         return null();
       }
 
-      RootedPropertyName bindingAtom(cx_, importedBinding());
+      const ParserName* bindingAtom = importedBinding();
       if (!bindingAtom) {
         return null();
       }
 
       NameNodeType bindingName = newName(bindingAtom);
       if (!bindingName) {
         return null();
       }
@@ -4902,40 +4943,41 @@ GeneralParser<ParseHandler, Unit>::impor
   if (tt == TokenKind::Dot || tt == TokenKind::LeftParen) {
     return expressionStatement(yieldHandling);
   }
 
   return importDeclaration();
 }
 
 template <typename Unit>
-bool Parser<FullParseHandler, Unit>::checkExportedName(JSAtom* exportName) {
+bool Parser<FullParseHandler, Unit>::checkExportedName(
+    const ParserAtom* exportName) {
   if (!pc_->sc()->asModuleContext()->builder.hasExportedName(exportName)) {
     return true;
   }
 
-  UniqueChars str = AtomToPrintableString(cx_, exportName);
+  UniqueChars str = ParserAtomToPrintableString(cx_, exportName);
   if (!str) {
     return false;
   }
 
   error(JSMSG_DUPLICATE_EXPORT_NAME, str.get());
   return false;
 }
 
 template <typename Unit>
 inline bool Parser<SyntaxParseHandler, Unit>::checkExportedName(
-    JSAtom* exportName) {
+    const ParserAtom* exportName) {
   MOZ_ALWAYS_FALSE(abortIfSyntaxParser());
   return false;
 }
 
 template <class ParseHandler, typename Unit>
 inline bool GeneralParser<ParseHandler, Unit>::checkExportedName(
-    JSAtom* exportName) {
+    const ParserAtom* exportName) {
   return asFinalParser()->checkExportedName(exportName);
 }
 
 template <typename Unit>
 bool Parser<FullParseHandler, Unit>::checkExportedNamesForArrayBinding(
     ListNode* array) {
   MOZ_ASSERT(array->isKind(ParseNodeKind::ArrayExpr));
 
@@ -5240,17 +5282,17 @@ GeneralParser<ParseHandler, Unit>::expor
     if (!exportName) {
       return null();
     }
 
     if (!checkExportedNameForClause(exportName)) {
       return null();
     }
 
-    NameNodeType importName = newName(cx_->names().star);
+    NameNodeType importName = newName(cx_->parserNames().star);
     if (!importName) {
       return null();
     }
 
     BinaryNodeType exportSpec = handler_.newExportSpec(importName, exportName);
     if (!exportSpec) {
       return null();
     }
@@ -5276,18 +5318,17 @@ GeneralParser<ParseHandler, Unit>::expor
 
 template <typename Unit>
 bool Parser<FullParseHandler, Unit>::checkLocalExportNames(ListNode* node) {
   // ES 2017 draft 15.2.3.1.
   for (ParseNode* next : node->contents()) {
     ParseNode* name = next->as<BinaryNode>().left();
     MOZ_ASSERT(name->isKind(ParseNodeKind::Name));
 
-    RootedPropertyName ident(cx_,
-                             name->as<NameNode>().atom()->asPropertyName());
+    const ParserName* ident = name->as<NameNode>().atom()->asName();
     if (!checkLocalExportName(ident, name->pn_pos.begin)) {
       return false;
     }
   }
 
   return true;
 }
 
@@ -5621,17 +5662,17 @@ GeneralParser<ParseHandler, Unit>::expor
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::BinaryNodeType
 GeneralParser<ParseHandler, Unit>::exportDefaultAssignExpr(uint32_t begin) {
   if (!abortIfSyntaxParser()) {
     return null();
   }
 
-  HandlePropertyName name = cx_->parserNames().default_;
+  const ParserName* name = cx_->parserNames().default_;
   NameNodeType nameNode = newName(name);
   if (!nameNode) {
     return null();
   }
   if (!noteDeclaredName(name, DeclarationKind::Const, pos())) {
     return null();
   }
 
@@ -6464,17 +6505,17 @@ GeneralParser<ParseHandler, Unit>::switc
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::ContinueStatementType
 GeneralParser<ParseHandler, Unit>::continueStatement(
     YieldHandling yieldHandling) {
   MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Continue));
   uint32_t begin = pos().begin;
 
-  RootedPropertyName label(cx_);
+  const ParserName* label = nullptr;
   if (!matchLabel(yieldHandling, &label)) {
     return null();
   }
 
   auto validity = pc_->checkContinueStatement(label);
   if (validity.isErr()) {
     switch (validity.unwrapErr()) {
       case ParseContext::ContinueStatementError::NotInALoop:
@@ -6495,17 +6536,17 @@ GeneralParser<ParseHandler, Unit>::conti
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::BreakStatementType
 GeneralParser<ParseHandler, Unit>::breakStatement(YieldHandling yieldHandling) {
   MOZ_ASSERT(anyChars.isCurrentTokenType(TokenKind::Break));
   uint32_t begin = pos().begin;
 
-  RootedPropertyName label(cx_);
+  const ParserName* label = nullptr;
   if (!matchLabel(yieldHandling, &label)) {
     return null();
   }
 
   auto validity = pc_->checkBreakStatement(label);
   if (validity.isErr()) {
     switch (validity.unwrapErr()) {
       case ParseContext::BreakStatementError::ToughBreak:
@@ -6690,17 +6731,17 @@ typename ParseHandler::Node GeneralParse
   anyChars.ungetToken();
   return statement(yieldHandling);
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::LabeledStatementType
 GeneralParser<ParseHandler, Unit>::labeledStatement(
     YieldHandling yieldHandling) {
-  RootedPropertyName label(cx_, labelIdentifier(yieldHandling));
+  const ParserName* label = labelIdentifier(yieldHandling);
   if (!label) {
     return null();
   }
 
   auto hasSameLabel = [&label](ParseContext::LabelStatement* stmt) {
     return stmt->label() == label;
   };
 
@@ -7021,17 +7062,17 @@ static AccessorType ToAccessorType(Prope
     default:
       MOZ_CRASH("unexpected property type");
   }
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::classMember(
     YieldHandling yieldHandling, const ParseContext::ClassStatement& classStmt,
-    HandlePropertyName className, uint32_t classStartOffset,
+    const ParserName* className, uint32_t classStartOffset,
     HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers,
     ListNodeType& classMembers, bool* done) {
   *done = false;
 
   TokenKind tt;
   if (!tokenStream.getToken(&tt, TokenStream::SlashIsInvalid)) {
     return false;
   }
@@ -7060,17 +7101,17 @@ bool GeneralParser<ParseHandler, Unit>::
     anyChars.ungetToken();
   }
 
   uint32_t propNameOffset;
   if (!tokenStream.peekOffset(&propNameOffset, TokenStream::SlashIsInvalid)) {
     return false;
   }
 
-  RootedAtom propAtom(cx_);
+  const ParserAtom* propAtom = nullptr;
   PropertyType propType;
   Node propName = propertyOrMethodName(yieldHandling, PropertyNameInClass,
                                        /* maybeDecl = */ Nothing(),
                                        classMembers, &propType, &propAtom);
   if (!propName) {
     return false;
   }
 
@@ -7088,17 +7129,17 @@ bool GeneralParser<ParseHandler, Unit>::
     }
 
     if (handler_.isPrivateName(propName)) {
       if (propAtom == cx_->parserNames().hashConstructor) {
         errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
         return false;
       }
 
-      RootedPropertyName privateName(cx_, propAtom->asPropertyName());
+      const ParserName* privateName = propAtom->asName();
       if (!noteDeclaredPrivateName(propName, privateName, propType, pos())) {
         return false;
       }
     }
 
     if (!abortIfSyntaxParser()) {
       return false;
     }
@@ -7150,17 +7191,17 @@ bool GeneralParser<ParseHandler, Unit>::
     propType = hasHeritage == HasHeritage::Yes
                    ? PropertyType::DerivedConstructor
                    : PropertyType::Constructor;
   } else if (isStatic && propAtom == cx_->parserNames().prototype) {
     errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
     return false;
   }
 
-  RootedAtom funName(cx_);
+  const ParserAtom* funName = nullptr;
   switch (propType) {
     case PropertyType::Getter:
     case PropertyType::Setter:
       if (!anyChars.isCurrentTokenType(TokenKind::RightBracket)) {
         funName = prefixAccessorName(propType, propAtom);
         if (!funName) {
           return false;
         }
@@ -7232,17 +7273,17 @@ bool GeneralParser<ParseHandler, Unit>::
       errorAt(propNameOffset, JSMSG_BAD_METHOD_DEF);
       return false;
     }
 
     if (!abortIfSyntaxParser()) {
       return false;
     }
 
-    RootedPropertyName privateName(cx_, propAtom->asPropertyName());
+    const ParserName* privateName = propAtom->asName();
     if (!noteDeclaredPrivateName(propName, privateName, propType, pos())) {
       return false;
     }
 
     // Private non-static methods are stamped onto every instance using
     // initializers. Private static methods are stored directly on the
     // constructor during class evaluation; see
     // BytecodeEmitter::emitPropertyList.
@@ -7269,19 +7310,19 @@ bool GeneralParser<ParseHandler, Unit>::
         case AccessorType::Setter:
           if (!storedMethodName.append(".setter")) {
             return false;
           }
           break;
         default:
           MOZ_CRASH("Invalid private method accessor type");
       }
-      RootedAtom storedMethodAtom(cx_, storedMethodName.finishAtom());
-      RootedPropertyName storedMethodProp(cx_,
-                                          storedMethodAtom->asPropertyName());
+      const ParserAtom* storedMethodAtom =
+          storedMethodName.finishParserAtom(this->compilationInfo_);
+      const ParserName* storedMethodProp = storedMethodAtom->asName();
       if (!noteDeclaredName(storedMethodProp, DeclarationKind::Const, pos())) {
         return false;
       }
 
       initializerIfPrivate =
           Some(privateMethodInitializer(propAtom, storedMethodAtom));
     }
   }
@@ -7300,17 +7341,17 @@ bool GeneralParser<ParseHandler, Unit>::
     dotInitializersScope.reset();
   }
 
   return handler_.addClassMemberDefinition(classMembers, method);
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::finishClassConstructor(
-    const ParseContext::ClassStatement& classStmt, HandlePropertyName className,
+    const ParseContext::ClassStatement& classStmt, const ParserName* className,
     HasHeritage hasHeritage, uint32_t classStartOffset, uint32_t classEndOffset,
     const ClassInitializedMembers& classInitializedMembers,
     ListNodeType& classMembers) {
   // Fields cannot re-use the constructor obtained via JSOp::ClassConstructor or
   // JSOp::DerivedConstructor due to needing to emit calls to the field
   // initializers in the constructor. So, synthesize a new one.
   size_t numPrivateMethods = classInitializedMembers.privateMethods;
   size_t numFields = classInitializedMembers.instanceFields;
@@ -7386,17 +7427,17 @@ GeneralParser<ParseHandler, Unit>::class
   uint32_t classStartOffset = pos().begin;
   bool savedStrictness = setLocalStrictMode(true);
 
   TokenKind tt;
   if (!tokenStream.getToken(&tt)) {
     return null();
   }
 
-  RootedPropertyName className(cx_);
+  const ParserName* className = nullptr;
   if (TokenKindIsPossibleIdentifier(tt)) {
     className = bindingIdentifier(yieldHandling);
     if (!className) {
       return null();
     }
   } else if (classContext == ClassStatement) {
     if (defaultHandling == AllowDefaultName) {
       className = cx_->parserNames().default_;
@@ -7561,17 +7602,18 @@ GeneralParser<ParseHandler, Unit>::class
   // We're leaving a class definition that was not itself nested within a class
   if (!isInClass) {
     mozilla::Maybe<UnboundPrivateName> maybeUnboundName;
     if (!this->getCompilationInfo().usedNames.hasUnboundPrivateNames(
             cx_, maybeUnboundName)) {
       return null();
     }
     if (maybeUnboundName) {
-      UniqueChars str = AtomToPrintableString(cx_, maybeUnboundName->atom);
+      UniqueChars str =
+          ParserAtomToPrintableString(cx_, maybeUnboundName->atom);
       if (!str) {
         return null();
       }
 
       errorAt(maybeUnboundName->position.begin, JSMSG_MISSING_PRIVATE_DECL,
               str.get());
       return null();
     }
@@ -7579,17 +7621,18 @@ GeneralParser<ParseHandler, Unit>::class
 
   return handler_.newClass(nameNode, classHeritage, classBlock,
                            TokenPos(classStartOffset, classEndOffset));
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::synthesizeConstructor(
-    HandleAtom className, uint32_t classNameOffset, HasHeritage hasHeritage) {
+    const ParserAtom* className, uint32_t classNameOffset,
+    HasHeritage hasHeritage) {
   FunctionSyntaxKind functionSyntaxKind =
       hasHeritage == HasHeritage::Yes
           ? FunctionSyntaxKind::DerivedClassConstructor
           : FunctionSyntaxKind::ClassConstructor;
 
   bool isSelfHosting = options().selfHostingMode;
   FunctionFlags flags =
       InitialFunctionFlags(functionSyntaxKind, GeneratorKind::NotGenerator,
@@ -7735,17 +7778,17 @@ GeneralParser<ParseHandler, Unit>::synth
   // initWithEnclosingParseContext.
 
   return funNode;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::privateMethodInitializer(
-    HandleAtom propAtom, HandleAtom storedMethodAtom) {
+    const ParserAtom* propAtom, const ParserAtom* storedMethodAtom) {
   // Synthesize an initializer function that the constructor can use to stamp a
   // private method onto an instance object.
   FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::FieldInitializer;
   FunctionAsyncKind asyncKind = FunctionAsyncKind::SyncFunction;
   GeneratorKind generatorKind = GeneratorKind::NotGenerator;
   bool isSelfHosting = options().selfHostingMode;
   FunctionFlags flags =
       InitialFunctionFlags(syntaxKind, generatorKind, asyncKind, isSelfHosting);
@@ -7782,21 +7825,21 @@ GeneralParser<ParseHandler, Unit>::priva
   handler_.setFunctionFormalParametersAndBody(funNode, argsbody);
   setFunctionStartAtCurrentToken(funbox);
   funbox->setArgCount(0);
   funbox->usesThis = true;
 
   // Note both the stored private method body and it's private name as being
   // used in the initializer. They will be emitted into the method body in the
   // BCE.
-  RootedPropertyName storedMethodName(cx_, storedMethodAtom->asPropertyName());
+  const ParserName* storedMethodName = storedMethodAtom->asName();
   if (!noteUsedName(storedMethodName)) {
     return null();
   }
-  RootedPropertyName privateName(cx_, propAtom->asPropertyName());
+  const ParserName* privateName = propAtom->asName();
   NameNodeType privateNameNode = privateNameReference(privateName);
   if (!privateNameNode) {
     return null();
   }
 
   bool canSkipLazyClosedOverBindings = handler_.canSkipLazyClosedOverBindings();
   if (!pc_->declareFunctionThis(usedNames_, canSkipLazyClosedOverBindings)) {
     return null();
@@ -7833,17 +7876,17 @@ GeneralParser<ParseHandler, Unit>::priva
   }
 
   return funNode;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::fieldInitializerOpt(
-    Node propName, HandleAtom propAtom,
+    Node propName, const ParserAtom* propAtom,
     ClassInitializedMembers& classInitializedMembers, bool isStatic,
     HasHeritage hasHeritage) {
   bool hasInitializer = false;
   if (!tokenStream.matchToken(&hasInitializer, TokenKind::Assign,
                               TokenStream::SlashIsDiv)) {
     return null();
   }
 
@@ -7994,17 +8037,17 @@ GeneralParser<ParseHandler, Unit>::field
     // HasHeritage::Yes we end up emitting CheckPrivateField, but otherwise we
     // emit InitElem -- this is an optimization to minimize HasOwn checks
     // in InitElem for classes without heritage.
     //
     // Further tweaking would be to ultimately only do CheckPrivateField for the
     // -first- field in a derived class, which would suffice to match the
     // semantic check.
 
-    RootedPropertyName privateName(cx_, propAtom->asPropertyName());
+    const ParserName* privateName = propAtom->asName();
     NameNodeType privateNameNode = privateNameReference(privateName);
     if (!privateNameNode) {
       return null();
     }
 
     propAssignFieldAccess = handler_.newPropertyByValue(
         propAssignThis, privateNameNode, wholeInitializerPos.end);
     if (!propAssignFieldAccess) {
@@ -8012,18 +8055,18 @@ GeneralParser<ParseHandler, Unit>::field
     }
   } else if (propAtom->isIndex(&indexValue)) {
     propAssignFieldAccess = handler_.newPropertyByValue(
         propAssignThis, propName, wholeInitializerPos.end);
     if (!propAssignFieldAccess) {
       return null();
     }
   } else {
-    NameNodeType propAssignName = handler_.newPropertyName(
-        propAtom->asPropertyName(), wholeInitializerPos);
+    NameNodeType propAssignName =
+        handler_.newPropertyName(propAtom->asName(), wholeInitializerPos);
     if (!propAssignName) {
       return null();
     }
 
     propAssignFieldAccess =
         handler_.newPropertyAccess(propAssignThis, propAssignName);
     if (!propAssignFieldAccess) {
       return null();
@@ -8893,17 +8936,17 @@ typename ParseHandler::Node GeneralParse
   // This only handles identifiers that *never* have special meaning anywhere
   // in the language.  Contextual keywords, reserved words in strict mode,
   // and other hard cases are handled outside this fast path.
   if (firstToken == TokenKind::Name) {
     if (!tokenStream.nextTokenEndsExpr(&endsExpr)) {
       return null();
     }
     if (endsExpr) {
-      Rooted<PropertyName*> name(cx_, identifierReference(yieldHandling));
+      const ParserName* name = identifierReference(yieldHandling);
       if (!name) {
         return null();
       }
 
       return identifierReference(name);
     }
   }
 
@@ -8956,17 +8999,17 @@ typename ParseHandler::Node GeneralParse
 
     TokenKind tokenAfterAsync;
     if (!tokenStream.getToken(&tokenAfterAsync)) {
       return null();
     }
     MOZ_ASSERT(TokenKindIsPossibleIdentifier(tokenAfterAsync));
 
     // Check yield validity here.
-    RootedPropertyName name(cx_, bindingIdentifier(yieldHandling));
+    const ParserName* name = bindingIdentifier(yieldHandling);
     if (!name) {
       return null();
     }
 
     if (!tokenStream.peekTokenSameLine(&tokenAfterLHS)) {
       return null();
     }
     if (tokenAfterLHS != TokenKind::Arrow) {
@@ -9749,46 +9792,46 @@ typename ParseHandler::Node GeneralParse
     return null();
   }
 
   return lhs;
 }
 
 template <class ParseHandler>
 inline typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::newName(PropertyName* name) {
+PerHandlerParser<ParseHandler>::newName(const ParserName* name) {
   return newName(name, pos());
 }
 
 template <class ParseHandler>
 inline typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::newName(PropertyName* name, TokenPos pos) {
+PerHandlerParser<ParseHandler>::newName(const ParserName* name, TokenPos pos) {
   return handler_.newName(name, pos, cx_);
 }
 
 template <class ParseHandler>
 inline typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::newPrivateName(PropertyName* name) {
+PerHandlerParser<ParseHandler>::newPrivateName(const ParserName* name) {
   return newPrivateName(name, pos());
 }
 
 template <class ParseHandler>
 inline typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::newPrivateName(PropertyName* name,
+PerHandlerParser<ParseHandler>::newPrivateName(const ParserName* name,
                                                TokenPos pos) {
   return handler_.newPrivateName(name, pos);
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::Node
 GeneralParser<ParseHandler, Unit>::memberPropertyAccess(
     Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) {
   MOZ_ASSERT(TokenKindIsPossibleIdentifierName(anyChars.currentToken().type) ||
              anyChars.currentToken().type == TokenKind::PrivateName);
-  PropertyName* field = anyChars.currentName();
+  const ParserName* field = anyChars.currentName();
   if (handler_.isSuperBase(lhs) && !checkAndMarkSuperScope()) {
     error(JSMSG_BAD_SUPERPROP, "property");
     return null();
   }
 
   NameNodeType name = handler_.newPropertyName(field, pos());
   if (!name) {
     return null();
@@ -9802,17 +9845,17 @@ GeneralParser<ParseHandler, Unit>::membe
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::Node
 GeneralParser<ParseHandler, Unit>::memberPrivateAccess(
     Node lhs, OptionalKind optionalKind /* = OptionalKind::NonOptional */) {
   MOZ_ASSERT(anyChars.currentToken().type == TokenKind::PrivateName);
 
-  RootedPropertyName field(cx_, anyChars.currentName());
+  const ParserName* field = anyChars.currentName();
   // Cannot access private fields on super.
   if (handler_.isSuperBase(lhs)) {
     error(JSMSG_BAD_SUPERPRIVATE);
     return null();
   }
 
   NameNodeType privateName = privateNameReference(field);
   if (!privateName) {
@@ -9889,17 +9932,17 @@ typename ParseHandler::Node GeneralParse
   }
 
   MOZ_ASSERT(tt == TokenKind::LeftParen || tt == TokenKind::TemplateHead ||
                  tt == TokenKind::NoSubsTemplate,
              "Unexpected token kind for member call");
 
   JSOp op = JSOp::Call;
   bool maybeAsyncArrow = false;
-  if (PropertyName* prop = handler_.maybeDottedProperty(lhs)) {
+  if (const ParserName* prop = handler_.maybeDottedProperty(lhs)) {
     // Use the JSOp::Fun{Apply,Call} optimizations given the right
     // syntax.
     if (prop == cx_->parserNames().apply) {
       op = JSOp::FunApply;
       if (pc_->isFunctionBox()) {
         pc_->functionBox()->usesApply = true;
       }
     } else if (prop == cx_->parserNames().call) {
@@ -9974,17 +10017,17 @@ typename ParseHandler::Node GeneralParse
     return null();
   }
 
   return handler_.newTaggedTemplate(lhs, args, op);
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::checkLabelOrIdentifierReference(
-    PropertyName* ident, uint32_t offset, YieldHandling yieldHandling,
+    const ParserName* ident, uint32_t offset, YieldHandling yieldHandling,
     TokenKind hint /* = TokenKind::Limit */) {
   TokenKind tt;
   if (hint == TokenKind::Limit) {
     tt = ReservedWordTokenKind(ident);
   } else {
     MOZ_ASSERT(hint == ReservedWordTokenKind(ident),
                "hint doesn't match actual token kind");
     tt = hint;
@@ -10052,17 +10095,17 @@ bool GeneralParser<ParseHandler, Unit>::
     return false;
   }
   MOZ_ASSERT_UNREACHABLE("Unexpected reserved word kind.");
   return false;
 }
 
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::checkBindingIdentifier(
-    PropertyName* ident, uint32_t offset, YieldHandling yieldHandling,
+    const ParserName* ident, uint32_t offset, YieldHandling yieldHandling,
     TokenKind hint /* = TokenKind::Limit */) {
   if (pc_->sc()->strict()) {
     if (ident == cx_->parserNames().arguments) {
       if (!strictModeErrorAt(offset, JSMSG_BAD_STRICT_ASSIGN, "arguments")) {
         return false;
       }
       return true;
     }
@@ -10074,71 +10117,69 @@ bool GeneralParser<ParseHandler, Unit>::
       return true;
     }
   }
 
   return checkLabelOrIdentifierReference(ident, offset, yieldHandling, hint);
 }
 
 template <class ParseHandler, typename Unit>
-PropertyName* GeneralParser<ParseHandler, Unit>::labelOrIdentifierReference(
+const ParserName* GeneralParser<ParseHandler, Unit>::labelOrIdentifierReference(
     YieldHandling yieldHandling) {
   // ES 2017 draft 12.1.1.
   //   StringValue of IdentifierName normalizes any Unicode escape sequences
   //   in IdentifierName hence such escapes cannot be used to write an
   //   Identifier whose code point sequence is the same as a ReservedWord.
   //
-  // Use PropertyName* instead of TokenKind to reflect the normalization.
+  // Use const ParserName* instead of TokenKind to reflect the normalization.
 
   // Unless the name contains escapes, we can reuse the current TokenKind
   // to determine if the name is a restricted identifier.
   TokenKind hint = !anyChars.currentNameHasEscapes()
                        ? anyChars.currentToken().type
                        : TokenKind::Limit;
-  RootedPropertyName ident(cx_, anyChars.currentName());
+  const ParserName* ident = anyChars.currentName();
   if (!checkLabelOrIdentifierReference(ident, pos().begin, yieldHandling,
                                        hint)) {
     return nullptr;
   }
   return ident;
 }
 
 template <class ParseHandler, typename Unit>
-PropertyName* GeneralParser<ParseHandler, Unit>::bindingIdentifier(
+const ParserName* GeneralParser<ParseHandler, Unit>::bindingIdentifier(
     YieldHandling yieldHandling) {
   TokenKind hint = !anyChars.currentNameHasEscapes()
                        ? anyChars.currentToken().type
                        : TokenKind::Limit;
-  RootedPropertyName ident(cx_, anyChars.currentName());
+  const ParserName* ident = anyChars.currentName();
   if (!checkBindingIdentifier(ident, pos().begin, yieldHandling, hint)) {
     return nullptr;
   }
   return ident;
 }
 
 template <class ParseHandler>
 typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::identifierReference(
-    Handle<PropertyName*> name) {
+PerHandlerParser<ParseHandler>::identifierReference(const ParserName* name) {
   NameNodeType id = newName(name);
   if (!id) {
     return null();
   }
 
   if (!noteUsedName(name)) {
     return null();
   }
 
   return id;
 }
 
 template <class ParseHandler>
 typename ParseHandler::NameNodeType
-PerHandlerParser<ParseHandler>::privateNameReference(
-    Handle<PropertyName*> name) {
+PerHandlerParser<ParseHandler>::privateNameReference(const ParserName* name) {
   NameNodeType id = newPrivateName(name);
   if (!id) {
     return null();
   }
 
   if (!noteUsedName(name, NameVisibility::Private, Some(pos()))) {
     return null();
   }
@@ -10274,26 +10315,38 @@ Parser<SyntaxParseHandler, Unit>::newBig
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::BigIntLiteralType
 GeneralParser<ParseHandler, Unit>::newBigInt() {
   return asFinalParser()->newBigInt();
 }
 
 template <class ParseHandler, typename Unit>
-JSAtom* GeneralParser<ParseHandler, Unit>::bigIntAtom() {
+const ParserAtom* GeneralParser<ParseHandler, Unit>::bigIntAtom() {
+  // TODO-Stencil: In progress on Bug 1659595.
+  //
+  // This implementation needs to be changed to do either a direct translation
+  // of the atom, or the parser in general fixed to not require normalized
+  // bigint atoms, and this code changed to return the raw character data as
+  // an atom (or more appropriately: this method removed and its user
+  // changed to just create a regular string atom).
+
   // See newBigInt() for a description about |chars'| contents.
   const auto& chars = tokenStream.getCharBuffer();
   mozilla::Range<const char16_t> source(chars.begin(), chars.length());
 
   RootedBigInt bi(cx_, js::ParseBigIntLiteral(cx_, source));
   if (!bi) {
     return nullptr;
   }
-  return BigIntToAtom<CanGC>(cx_, bi);
+  RootedAtom atom(cx_, BigIntToAtom<CanGC>(cx_, bi));
+  if (!atom) {
+    return nullptr;
+  }
+  return this->compilationInfo_.lowerJSAtomToParserAtom(atom.get());
 }
 
 // |exprPossibleError| is the PossibleError state within |expr|,
 // |possibleError| is the surrounding PossibleError state.
 template <class ParseHandler, typename Unit>
 bool GeneralParser<ParseHandler, Unit>::checkDestructuringAssignmentTarget(
     Node expr, TokenPos exprPos, PossibleError* exprPossibleError,
     PossibleError* possibleError, TargetBehavior behavior) {
@@ -10534,75 +10587,78 @@ GeneralParser<ParseHandler, Unit>::array
   handler_.setEndPosition(literal, pos().end);
   return literal;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::Node GeneralParser<ParseHandler, Unit>::propertyName(
     YieldHandling yieldHandling, PropertyNameContext propertyNameContext,
     const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList,
-    MutableHandleAtom propAtom) {
+    const ParserAtom** propAtomOut) {
   // PropertyName[Yield, Await]:
   //   LiteralPropertyName
   //   ComputedPropertyName[?Yield, ?Await]
   //
   // LiteralPropertyName:
   //   IdentifierName
   //   StringLiteral
   //   NumericLiteral
   TokenKind ltok = anyChars.currentToken().type;
 
-  propAtom.set(nullptr);
+  *propAtomOut = nullptr;
   switch (ltok) {
-    case TokenKind::Number:
-      propAtom.set(NumberToAtom(cx_, anyChars.currentToken().number()));
-      if (!propAtom.get()) {
-        return null();
-      }
+    case TokenKind::Number: {
+      const ParserAtom* numAtom = NumberToParserAtom(
+          this->compilationInfo_, anyChars.currentToken().number());
+      if (!numAtom) {
+        return null();
+      }
+      *propAtomOut = numAtom;
       return newNumber(anyChars.currentToken());
+    }
 
     case TokenKind::BigInt:
-      propAtom.set(bigIntAtom());
-      if (!propAtom.get()) {
+      *propAtomOut = bigIntAtom();
+      if (!*propAtomOut) {
         return null();
       }
       return newBigInt();
 
     case TokenKind::String: {
-      propAtom.set(anyChars.currentToken().atom());
+      *propAtomOut = anyChars.currentToken().atom();
       uint32_t index;
-      if (propAtom->isIndex(&index)) {
+      if ((*propAtomOut)->isIndex(&index)) {
         return handler_.newNumber(index, NoDecimal, pos());
       }
       return stringLiteral();
     }
 
     case TokenKind::LeftBracket:
       return computedPropertyName(yieldHandling, maybeDecl, propertyNameContext,
                                   propList);
 
     case TokenKind::PrivateName: {
       if (propertyNameContext != PropertyNameContext::PropertyNameInClass) {
         error(JSMSG_ILLEGAL_PRIVATE_FIELD);
         return null();
       }
 
-      propAtom.set(anyChars.currentName());
-      RootedPropertyName propName(cx_, propAtom->asPropertyName());
+      const ParserName* propName = anyChars.currentName()->asName();
+      *propAtomOut = propName;
       return privateNameReference(propName);
     }
 
     default: {
       if (!TokenKindIsPossibleIdentifierName(ltok)) {
         error(JSMSG_UNEXPECTED_TOKEN, "property name", TokenKindToDesc(ltok));
         return null();
       }
 
-      propAtom.set(anyChars.currentName());
-      return handler_.newObjectLiteralPropertyName(propAtom, pos());
+      *propAtomOut = anyChars.currentName();
+      return handler_.newObjectLiteralPropertyName(*propAtomOut, pos());
     }
   }
 }
 
 // True if `kind` can be the first token of a PropertyName.
 static bool TokenKindCanStartPropertyName(TokenKind tt) {
   return TokenKindIsPossibleIdentifierName(tt) || tt == TokenKind::String ||
          tt == TokenKind::Number || tt == TokenKind::LeftBracket ||
@@ -10610,17 +10666,17 @@ static bool TokenKindCanStartPropertyNam
          tt == TokenKind::PrivateName;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::Node
 GeneralParser<ParseHandler, Unit>::propertyOrMethodName(
     YieldHandling yieldHandling, PropertyNameContext propertyNameContext,
     const Maybe<DeclarationKind>& maybeDecl, ListNodeType propList,
-    PropertyType* propType, MutableHandleAtom propAtom) {
+    PropertyType* propType, const ParserAtom** propAtomOut) {
   // We're parsing an object literal, class, or destructuring pattern;
   // propertyNameContext tells which one. This method parses any of the
   // following, storing the corresponding PropertyType in `*propType` to tell
   // the caller what we parsed:
   //
   //     async [no LineTerminator here] PropertyName
   //                            ==> PropertyType::AsyncMethod
   //     async [no LineTerminator here] * PropertyName
@@ -10694,17 +10750,17 @@ GeneralParser<ParseHandler, Unit>::prope
     if (TokenKindCanStartPropertyName(tt)) {
       tokenStream.consumeKnownToken(tt);
       isGetter = (ltok == TokenKind::Get);
       isSetter = (ltok == TokenKind::Set);
     }
   }
 
   Node propName = propertyName(yieldHandling, propertyNameContext, maybeDecl,
-                               propList, propAtom);
+                               propList, propAtomOut);
   if (!propName) {
     return null();
   }
 
   // Grab the next token following the property/method name.
   // (If this isn't a colon, we're going to either put it back or throw.)
   TokenKind tt;
   if (!tokenStream.getToken(&tt)) {
@@ -10808,17 +10864,17 @@ GeneralParser<ParseHandler, Unit>::objec
   ListNodeType literal = handler_.newObjectLiteral(pos().begin);
   if (!literal) {
     return null();
   }
 
   bool seenPrototypeMutation = false;
   bool seenCoverInitializedName = false;
   Maybe<DeclarationKind> declKind = Nothing();
-  RootedAtom propAtom(cx_);
+  const ParserAtom* propAtom = nullptr;
   for (;;) {
     TokenKind tt;
     if (!tokenStream.peekToken(&tt)) {
       return null();
     }
     if (tt == TokenKind::RightCurly) {
       break;
     }
@@ -10910,17 +10966,17 @@ GeneralParser<ParseHandler, Unit>::objec
           handler_.addPropertyDefinition(literal, propDef);
         }
       } else if (propType == PropertyType::Shorthand) {
         /*
          * Support, e.g., |({x, y} = o)| as destructuring shorthand
          * for |({x: x, y: y} = o)|, and |var o = {x, y}| as
          * initializer shorthand for |var o = {x: x, y: y}|.
          */
-        Rooted<PropertyName*> name(cx_, identifierReference(yieldHandling));
+        const ParserName* name = identifierReference(yieldHandling);
         if (!name) {
           return null();
         }
 
         NameNodeType nameExpr = identifierReference(name);
         if (!nameExpr) {
           return null();
         }
@@ -10933,17 +10989,17 @@ GeneralParser<ParseHandler, Unit>::objec
                                    nameExpr)) {
           return null();
         }
       } else if (propType == PropertyType::CoverInitializedName) {
         /*
          * Support, e.g., |({x=1, y=2} = o)| as destructuring
          * shorthand with default values, as per ES6 12.14.5
          */
-        Rooted<PropertyName*> name(cx_, identifierReference(yieldHandling));
+        const ParserName* name = identifierReference(yieldHandling);
         if (!name) {
           return null();
         }
 
         Node lhs = identifierReference(name);
         if (!lhs) {
           return null();
         }
@@ -10991,17 +11047,17 @@ GeneralParser<ParseHandler, Unit>::objec
         if (!propExpr) {
           return null();
         }
 
         if (!handler_.addPropertyDefinition(literal, propName, propExpr)) {
           return null();
         }
       } else {
-        RootedAtom funName(cx_);
+        const ParserAtom* funName = nullptr;
         if (!anyChars.isCurrentTokenType(TokenKind::RightBracket)) {
           funName = propAtom;
 
           if (propType == PropertyType::Getter ||
               propType == PropertyType::Setter) {
             funName = prefixAccessorName(propType, propAtom);
             if (!funName) {
               return null();
@@ -11053,17 +11109,17 @@ GeneralParser<ParseHandler, Unit>::objec
   handler_.setEndPosition(literal, pos().end);
   return literal;
 }
 
 template <class ParseHandler, typename Unit>
 typename ParseHandler::FunctionNodeType
 GeneralParser<ParseHandler, Unit>::methodDefinition(uint32_t toStringStart,
                                                     PropertyType propType,
-                                                    HandleAtom funName) {
+                                                    const ParserAtom* funName) {
   FunctionSyntaxKind syntaxKind;
   switch (propType) {
     case PropertyType::Getter:
       syntaxKind = FunctionSyntaxKind::Getter;
       break;
 
     case PropertyType::Setter:
       syntaxKind = FunctionSyntaxKind::Setter;
@@ -11297,17 +11353,17 @@ typename ParseHandler::Node GeneralParse
         if (nextSameLine == TokenKind::Function) {
           uint32_t toStringStart = pos().begin;
           tokenStream.consumeKnownToken(TokenKind::Function);
           return functionExpr(toStringStart, PredictUninvoked,
                               FunctionAsyncKind::AsyncFunction);
         }
       }
 
-      Rooted<PropertyName*> name(cx_, identifierReference(yieldHandling));
+      const ParserName* name = identifierReference(yieldHandling);
       if (!name) {
         return null();
       }
 
       return identifierReference(name);
     }
 
     case TokenKind::RegExp:
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -363,25 +363,25 @@ class MOZ_STACK_CLASS ParserBase : publi
   using Base::warningNoOffset;
   using Base::warningWithNotes;
   using Base::warningWithNotesAt;
   using Base::warningWithNotesNoOffset;
 
  public:
   bool isUnexpectedEOF() const { return isUnexpectedEOF_; }
 
-  bool isValidStrictBinding(PropertyName* name);
+  bool isValidStrictBinding(const ParserName* name);
 
   bool hasValidSimpleStrictParameterNames();
 
   /*
    * Create a new function object given a name (which is optional if this is
    * a function expression).
    */
-  JSFunction* newFunction(HandleAtom atom, FunctionSyntaxKind kind,
+  JSFunction* newFunction(const ParserAtom* atom, FunctionSyntaxKind kind,
                           GeneratorKind generatorKind,
                           FunctionAsyncKind asyncKind);
 
   // A Parser::Mark is the extension of the LifoAlloc::Mark to the entire
   // Parser's state. Note: clients must still take care that any ParseContext
   // that points into released ParseNodes is destroyed.
   class Mark {
     friend class ParserBase;
@@ -395,44 +395,47 @@ class MOZ_STACK_CLASS ParserBase : publi
     return m;
   }
   void release(Mark m) {
     alloc_.release(m.mark);
     compilationInfo_.rewind(m.token);
   }
 
  public:
-  mozilla::Maybe<GlobalScope::Data*> newGlobalScopeData(
+  mozilla::Maybe<ParserGlobalScopeData*> newGlobalScopeData(
       ParseContext::Scope& scope);
-  mozilla::Maybe<ModuleScope::Data*> newModuleScopeData(
+  mozilla::Maybe<ParserModuleScopeData*> newModuleScopeData(
+      ParseContext::Scope& scope);
+  mozilla::Maybe<ParserEvalScopeData*> newEvalScopeData(
       ParseContext::Scope& scope);
-  mozilla::Maybe<EvalScope::Data*> newEvalScopeData(ParseContext::Scope& scope);
-  mozilla::Maybe<FunctionScope::Data*> newFunctionScopeData(
+  mozilla::Maybe<ParserFunctionScopeData*> newFunctionScopeData(
       ParseContext::Scope& scope, bool hasParameterExprs);
-  mozilla::Maybe<VarScope::Data*> newVarScopeData(ParseContext::Scope& scope);
-  mozilla::Maybe<LexicalScope::Data*> newLexicalScopeData(
+  mozilla::Maybe<ParserVarScopeData*> newVarScopeData(
+      ParseContext::Scope& scope);
+  mozilla::Maybe<ParserLexicalScopeData*> newLexicalScopeData(
       ParseContext::Scope& scope);
 
  protected:
   enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true };
   enum ForInitLocation { InForInit, NotInForInit };
 
   // While on a |let| Name token, examine |next| (which must already be
   // gotten).  Indicate whether |next|, the next token already gotten with
   // modifier TokenStream::SlashIsDiv, continues a LexicalDeclaration.
   bool nextTokenContinuesLetDeclaration(TokenKind next);
 
-  bool noteUsedNameInternal(HandlePropertyName name, NameVisibility visibility,
+  bool noteUsedNameInternal(const ParserName* name, NameVisibility visibility,
                             mozilla::Maybe<TokenPos> tokenPosition);
 
   bool checkAndMarkSuperScope();
 
   bool leaveInnerFunction(ParseContext* outerpc);
 
-  JSAtom* prefixAccessorName(PropertyType propType, HandleAtom propAtom);
+  const ParserAtom* prefixAccessorName(PropertyType propType,
+                                       const ParserAtom* propAtom);
 
   MOZ_MUST_USE bool setSourceMapInfo();
 
   void setFunctionEndFromCurrentToken(FunctionBox* funbox) const;
 };
 
 template <class ParseHandler>
 class MOZ_STACK_CLASS PerHandlerParser : public ParserBase {
@@ -489,17 +492,17 @@ class MOZ_STACK_CLASS PerHandlerParser :
   NameNodeType stringLiteral();
 
   const char* nameIsArgumentsOrEval(Node node);
 
   bool noteDestructuredPositionalFormalParameter(FunctionNodeType funNode,
                                                  Node destruct);
 
   bool noteUsedName(
-      HandlePropertyName name,
+      const ParserName* name,
       NameVisibility visibility = NameVisibility::Public,
       mozilla::Maybe<TokenPos> tokenPosition = mozilla::Nothing()) {
     // If the we are delazifying, the BaseScript already has all the closed-over
     // info for bindings and there's no need to track used names.
     if (handler_.canSkipLazyClosedOverBindings()) {
       return true;
     }
 
@@ -511,28 +514,28 @@ class MOZ_STACK_CLASS PerHandlerParser :
 
   bool checkForUndefinedPrivateFields(EvalSharedContext* evalSc = nullptr);
 
   bool finishFunctionScopes(bool isStandaloneFunction);
   LexicalScopeNodeType finishLexicalScope(ParseContext::Scope& scope, Node body,
                                           ScopeKind kind = ScopeKind::Lexical);
   bool finishFunction(bool isStandaloneFunction = false);
 
-  inline NameNodeType newName(PropertyName* name);
-  inline NameNodeType newName(PropertyName* name, TokenPos pos);
+  inline NameNodeType newName(const ParserName* name);
+  inline NameNodeType newName(const ParserName* name, TokenPos pos);
 
-  inline NameNodeType newPrivateName(PropertyName* name);
-  inline NameNodeType newPrivateName(PropertyName* name, TokenPos pos);
+  inline NameNodeType newPrivateName(const ParserName* name);
+  inline NameNodeType newPrivateName(const ParserName* name, TokenPos pos);
 
-  NameNodeType newInternalDotName(HandlePropertyName name);
+  NameNodeType newInternalDotName(const ParserName* name);
   NameNodeType newThisName();
   NameNodeType newDotGeneratorName();
 
-  NameNodeType identifierReference(Handle<PropertyName*> name);
-  NameNodeType privateNameReference(Handle<PropertyName*> name);
+  NameNodeType identifierReference(const ParserName* name);
+  NameNodeType privateNameReference(const ParserName* name);
 
   Node noSubstitutionTaggedTemplate();
 
   inline bool processExport(Node node);
   inline bool processExportFrom(BinaryNodeType node);
 
   // If ParseHandler is SyntaxParseHandler:
   //   Do nothing.
@@ -558,25 +561,26 @@ class MOZ_STACK_CLASS PerHandlerParser :
 
   // If ParseHandler is SyntaxParseHandler:
   //   Clear whether the last syntax parse was aborted.
   // If ParseHandler is FullParseHandler:
   //   Do nothing.
   inline void clearAbortedSyntaxParse();
 
  public:
-  NameNodeType newPropertyName(PropertyName* key, const TokenPos& pos) {
+  NameNodeType newPropertyName(const ParserName* key, const TokenPos& pos) {
     return handler_.newPropertyName(key, pos);
   }
 
   PropertyAccessType newPropertyAccess(Node expr, NameNodeType key) {
     return handler_.newPropertyAccess(expr, key);
   }
 
-  FunctionBox* newFunctionBox(FunctionNodeType funNode, JSAtom* explicitName,
+  FunctionBox* newFunctionBox(FunctionNodeType funNode,
+                              const ParserAtom* explicitName,
                               FunctionFlags flags, uint32_t toStringStart,
                               Directives directives,
                               GeneratorKind generatorKind,
                               FunctionAsyncKind asyncKind,
                               TopLevelFunction isTopLevel);
 
  public:
   // ErrorReportMixin.
@@ -984,18 +988,18 @@ class MOZ_STACK_CLASS GeneralParser : pu
   ListNodeType templateLiteral(YieldHandling yieldHandling);
   bool taggedTemplate(YieldHandling yieldHandling, ListNodeType tagArgsList,
                       TokenKind tt);
   bool appendToCallSiteObj(CallSiteNodeType callSiteObj);
   bool addExprAndGetNextTemplStrToken(YieldHandling yieldHandling,
                                       ListNodeType nodeList, TokenKind* ttp);
 
   inline bool trySyntaxParseInnerFunction(
-      FunctionNodeType* funNode, HandleAtom explicitName, FunctionFlags flags,
-      uint32_t toStringStart, InHandling inHandling,
+      FunctionNodeType* funNode, const ParserAtom* explicitName,
+      FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
       YieldHandling yieldHandling, FunctionSyntaxKind kind,
       GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
       Directives inheritedDirectives, Directives* newDirectives);
 
   inline bool skipLazyInnerFunction(FunctionNodeType funNode,
                                     uint32_t toStringStart,
                                     FunctionSyntaxKind kind, bool tryAnnexB);
 
@@ -1190,29 +1194,30 @@ class MOZ_STACK_CLASS GeneralParser : pu
                     TripledotHandling tripledotHandling,
                     PossibleError* possibleError = nullptr);
 
   bool tryNewTarget(BinaryNodeType* newTarget);
 
   BinaryNodeType importExpr(YieldHandling yieldHandling, bool allowCallSyntax);
 
   FunctionNodeType methodDefinition(uint32_t toStringStart,
-                                    PropertyType propType, HandleAtom funName);
+                                    PropertyType propType,
+                                    const ParserAtom* funName);
 
   /*
    * Additional JS parsers.
    */
   bool functionArguments(YieldHandling yieldHandling, FunctionSyntaxKind kind,
                          FunctionNodeType funNode);
 
   FunctionNodeType functionDefinition(
       FunctionNodeType funNode, uint32_t toStringStart, InHandling inHandling,
-      YieldHandling yieldHandling, HandleAtom name, FunctionSyntaxKind kind,
-      GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
-      bool tryAnnexB = false);
+      YieldHandling yieldHandling, const ParserAtom* name,
+      FunctionSyntaxKind kind, GeneratorKind generatorKind,
+      FunctionAsyncKind asyncKind, bool tryAnnexB = false);
 
   // Parse a function body.  Pass StatementListBody if the body is a list of
   // statements; pass ExpressionBody if the body is a single expression.
   enum FunctionBodyType { StatementListBody, ExpressionBody };
   LexicalScopeNodeType functionBody(InHandling inHandling,
                                     YieldHandling yieldHandling,
                                     FunctionSyntaxKind kind,
                                     FunctionBodyType type);
@@ -1225,17 +1230,17 @@ class MOZ_STACK_CLASS GeneralParser : pu
   ListNodeType argumentList(YieldHandling yieldHandling, bool* isSpread,
                             PossibleError* possibleError = nullptr);
   Node destructuringDeclaration(DeclarationKind kind,
                                 YieldHandling yieldHandling, TokenKind tt);
   Node destructuringDeclarationWithoutYieldOrAwait(DeclarationKind kind,
                                                    YieldHandling yieldHandling,
                                                    TokenKind tt);
 
-  inline bool checkExportedName(JSAtom* exportName);
+  inline bool checkExportedName(const ParserAtom* exportName);
   inline bool checkExportedNamesForArrayBinding(ListNodeType array);
   inline bool checkExportedNamesForObjectBinding(ListNodeType obj);
   inline bool checkExportedNamesForDeclaration(Node node);
   inline bool checkExportedNamesForDeclarationList(ListNodeType node);
   inline bool checkExportedNameForFunction(FunctionNodeType funNode);
   inline bool checkExportedNameForClass(ClassNodeType classNode);
   inline bool checkExportedNameForClause(NameNodeType nameNode);
 
@@ -1257,85 +1262,84 @@ class MOZ_STACK_CLASS GeneralParser : pu
     size_t staticFieldKeys = 0;
 
     // The number of instance class private methods.
     size_t privateMethods = 0;
   };
   MOZ_MUST_USE bool classMember(
       YieldHandling yieldHandling,
       const ParseContext::ClassStatement& classStmt,
-      HandlePropertyName className, uint32_t classStartOffset,
+      const ParserName* className, uint32_t classStartOffset,
       HasHeritage hasHeritage, ClassInitializedMembers& classInitializedMembers,
       ListNodeType& classMembers, bool* done);
   MOZ_MUST_USE bool finishClassConstructor(
       const ParseContext::ClassStatement& classStmt,
-      HandlePropertyName className, HasHeritage hasHeritage,
+      const ParserName* className, HasHeritage hasHeritage,
       uint32_t classStartOffset, uint32_t classEndOffset,
       const ClassInitializedMembers& classInitializedMembers,
       ListNodeType& classMembers);
 
-  FunctionNodeType privateMethodInitializer(HandleAtom propAtom,
-                                            HandleAtom storedMethodAtom);
+  FunctionNodeType privateMethodInitializer(const ParserAtom* propAtom,
+                                            const ParserAtom* storedMethodAtom);
   FunctionNodeType fieldInitializerOpt(
-      Node name, HandleAtom atom,
+      Node name, const ParserAtom* atom,
       ClassInitializedMembers& classInitializedMembers, bool isStatic,
       HasHeritage hasHeritage);
-  FunctionNodeType synthesizeConstructor(HandleAtom className,
+  FunctionNodeType synthesizeConstructor(const ParserAtom* className,
                                          uint32_t classNameOffset,
                                          HasHeritage hasHeritage);
 
-  bool checkBindingIdentifier(PropertyName* ident, uint32_t offset,
+  bool checkBindingIdentifier(const ParserName* ident, uint32_t offset,
                               YieldHandling yieldHandling,
                               TokenKind hint = TokenKind::Limit);
 
-  PropertyName* labelOrIdentifierReference(YieldHandling yieldHandling);
+  const ParserName* labelOrIdentifierReference(YieldHandling yieldHandling);
 
-  PropertyName* labelIdentifier(YieldHandling yieldHandling) {
+  const ParserName* labelIdentifier(YieldHandling yieldHandling) {
     return labelOrIdentifierReference(yieldHandling);
   }
 
-  PropertyName* identifierReference(YieldHandling yieldHandling) {
+  const ParserName* identifierReference(YieldHandling yieldHandling) {
     return labelOrIdentifierReference(yieldHandling);
   }
 
-  bool matchLabel(YieldHandling yieldHandling,
-                  MutableHandle<PropertyName*> label);
+  bool matchLabel(YieldHandling yieldHandling, const ParserName** labelOut);
 
   // Indicate if the next token (tokenized with SlashIsRegExp) is |in| or |of|.
   // If so, consume it.
   bool matchInOrOf(bool* isForInp, bool* isForOfp);
 
  private:
   bool checkIncDecOperand(Node operand, uint32_t operandOffset);
   bool checkStrictAssignment(Node lhs);
 
   void reportMissingClosing(unsigned errorNumber, unsigned noteNumber,
                             uint32_t openedPos);
 
-  void reportRedeclaration(HandlePropertyName name, DeclarationKind prevKind,
+  void reportRedeclaration(const ParserName* name, DeclarationKind prevKind,
                            TokenPos pos, uint32_t prevPos);
   bool notePositionalFormalParameter(FunctionNodeType funNode,
-                                     HandlePropertyName name, uint32_t beginPos,
+                                     const ParserName* name, uint32_t beginPos,
                                      bool disallowDuplicateParams,
                                      bool* duplicatedParam);
 
   enum PropertyNameContext {
     PropertyNameInLiteral,
     PropertyNameInPattern,
     PropertyNameInClass
   };
   Node propertyName(YieldHandling yieldHandling,
                     PropertyNameContext propertyNameContext,
                     const mozilla::Maybe<DeclarationKind>& maybeDecl,
-                    ListNodeType propList, MutableHandleAtom propAtom);
+                    ListNodeType propList, const ParserAtom** propAtomOut);
   Node propertyOrMethodName(YieldHandling yieldHandling,
                             PropertyNameContext propertyNameContext,
                             const mozilla::Maybe<DeclarationKind>& maybeDecl,
                             ListNodeType propList, PropertyType* propType,
-                            MutableHandleAtom propAtom);
+                            const ParserAtom** propAtomOut);
   UnaryNodeType computedPropertyName(
       YieldHandling yieldHandling,
       const mozilla::Maybe<DeclarationKind>& maybeDecl,
       PropertyNameContext propertyNameContext, ListNodeType literal);
   ListNodeType arrayInitializer(YieldHandling yieldHandling,
                                 PossibleError* possibleError);
   inline RegExpLiteralType newRegExp();
 
@@ -1368,17 +1372,17 @@ class MOZ_STACK_CLASS GeneralParser : pu
                                            PossibleError* possibleError);
 
   NumericLiteralType newNumber(const Token& tok) {
     return handler_.newNumber(tok.number(), tok.decimalPoint(), tok.pos);
   }
 
   inline BigIntLiteralType newBigInt();
 
-  JSAtom* bigIntAtom();
+  const ParserAtom* bigIntAtom();
 
   enum class OptionalKind {
     NonOptional = 0,
     Optional,
   };
   Node memberPropertyAccess(
       Node lhs, OptionalKind optionalKind = OptionalKind::NonOptional);
   Node memberPrivateAccess(
@@ -1389,47 +1393,48 @@ class MOZ_STACK_CLASS GeneralParser : pu
   Node memberCall(TokenKind tt, Node lhs, YieldHandling yieldHandling,
                   PossibleError* possibleError,
                   OptionalKind optionalKind = OptionalKind::NonOptional);
 
  protected:
   // Match the current token against the BindingIdentifier production with
   // the given Yield parameter.  If there is no match, report a syntax
   // error.
-  PropertyName* bindingIdentifier(YieldHandling yieldHandling);
+  const ParserName* bindingIdentifier(YieldHandling yieldHandling);
 
-  bool checkLabelOrIdentifierReference(PropertyName* ident, uint32_t offset,
+  bool checkLabelOrIdentifierReference(const ParserName* ident, uint32_t offset,
                                        YieldHandling yieldHandling,
                                        TokenKind hint = TokenKind::Limit);
 
   ListNodeType statementList(YieldHandling yieldHandling);
 
   MOZ_MUST_USE FunctionNodeType innerFunction(
-      FunctionNodeType funNode, ParseContext* outerpc, HandleAtom explicitName,
-      FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
+      FunctionNodeType funNode, ParseContext* outerpc,
+      const ParserAtom* explicitName, FunctionFlags flags,
+      uint32_t toStringStart, InHandling inHandling,
       YieldHandling yieldHandling, FunctionSyntaxKind kind,
       GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
       Directives inheritedDirectives, Directives* newDirectives);
 
   // Implements Automatic Semicolon Insertion.
   //
   // Use this to match `;` in contexts where ASI is allowed. Call this after
   // ruling out all other possibilities except `;`, by peeking ahead if
   // necessary.
   //
   // Unlike most optional Modifiers, this method's `modifier` argument defaults
   // to SlashIsRegExp, since that's by far the most common case: usually an
   // optional semicolon is at the end of a statement or declaration, and the
   // next token could be a RegExp literal beginning a new ExpressionStatement.
   bool matchOrInsertSemicolon(Modifier modifier = TokenStream::SlashIsRegExp);
 
-  bool noteDeclaredName(HandlePropertyName name, DeclarationKind kind,
+  bool noteDeclaredName(const ParserName* name, DeclarationKind kind,
                         TokenPos pos);
 
-  bool noteDeclaredPrivateName(Node nameNode, HandlePropertyName name,
+  bool noteDeclaredPrivateName(Node nameNode, const ParserName* name,
                                PropertyType propType, TokenPos pos);
 
  private:
   inline bool asmJS(ListNodeType list);
 };
 
 template <typename Unit>
 class MOZ_STACK_CLASS Parser<SyntaxParseHandler, Unit> final
@@ -1523,45 +1528,45 @@ class MOZ_STACK_CLASS Parser<SyntaxParse
   using Base::abortIfSyntaxParser;
   using Base::disableSyntaxParser;
 
  public:
   // Functions with multiple overloads of different visibility.  We can't
   // |using| the whole thing into existence because of the visibility
   // distinction, so we instead must manually delegate the required overload.
 
-  PropertyName* bindingIdentifier(YieldHandling yieldHandling) {
+  const ParserName* bindingIdentifier(YieldHandling yieldHandling) {
     return Base::bindingIdentifier(yieldHandling);
   }
 
   // Functions present in both Parser<ParseHandler, Unit> specializations.
 
   inline void setAwaitHandling(AwaitHandling awaitHandling);
   inline void setInParametersOfAsyncFunction(bool inParameters);
 
   RegExpLiteralType newRegExp();
   BigIntLiteralType newBigInt();
 
   // Parse a module.
   ModuleNodeType moduleBody(ModuleSharedContext* modulesc);
 
   inline BinaryNodeType importDeclaration();
   inline bool checkLocalExportNames(ListNodeType node);
-  inline bool checkExportedName(JSAtom* exportName);
+  inline bool checkExportedName(const ParserAtom* exportName);
   inline bool checkExportedNamesForArrayBinding(ListNodeType array);
   inline bool checkExportedNamesForObjectBinding(ListNodeType obj);
   inline bool checkExportedNamesForDeclaration(Node node);
   inline bool checkExportedNamesForDeclarationList(ListNodeType node);
   inline bool checkExportedNameForFunction(FunctionNodeType funNode);
   inline bool checkExportedNameForClass(ClassNodeType classNode);
   inline bool checkExportedNameForClause(NameNodeType nameNode);
 
   bool trySyntaxParseInnerFunction(
-      FunctionNodeType* funNode, HandleAtom explicitName, FunctionFlags flags,
-      uint32_t toStringStart, InHandling inHandling,
+      FunctionNodeType* funNode, const ParserAtom* explicitName,
+      FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
       YieldHandling yieldHandling, FunctionSyntaxKind kind,
       GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
       Directives inheritedDirectives, Directives* newDirectives);
 
   bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart,
                              FunctionSyntaxKind kind, bool tryAnnexB);
 
   bool asmJS(ListNodeType list);
@@ -1667,17 +1672,17 @@ class MOZ_STACK_CLASS Parser<FullParseHa
   using Base::disableSyntaxParser;
   using Base::getSyntaxParser;
 
  public:
   // Functions with multiple overloads of different visibility.  We can't
   // |using| the whole thing into existence because of the visibility
   // distinction, so we instead must manually delegate the required overload.
 
-  PropertyName* bindingIdentifier(YieldHandling yieldHandling) {
+  const ParserName* bindingIdentifier(YieldHandling yieldHandling) {
     return Base::bindingIdentifier(yieldHandling);
   }
 
   // Functions present in both Parser<ParseHandler, Unit> specializations.
 
   friend class AutoAwaitIsKeyword<SyntaxParseHandler, Unit>;
   inline void setAwaitHandling(AwaitHandling awaitHandling);
 
@@ -1687,28 +1692,28 @@ class MOZ_STACK_CLASS Parser<FullParseHa
   RegExpLiteralType newRegExp();
   BigIntLiteralType newBigInt();
 
   // Parse a module.
   ModuleNodeType moduleBody(ModuleSharedContext* modulesc);
 
   BinaryNodeType importDeclaration();
   bool checkLocalExportNames(ListNodeType node);
-  bool checkExportedName(JSAtom* exportName);
+  bool checkExportedName(const ParserAtom* exportName);
   bool checkExportedNamesForArrayBinding(ListNodeType array);
   bool checkExportedNamesForObjectBinding(ListNodeType obj);
   bool checkExportedNamesForDeclaration(Node node);
   bool checkExportedNamesForDeclarationList(ListNodeType node);
   bool checkExportedNameForFunction(FunctionNodeType funNode);
   bool checkExportedNameForClass(ClassNodeType classNode);
   inline bool checkExportedNameForClause(NameNodeType nameNode);
 
   bool trySyntaxParseInnerFunction(
-      FunctionNodeType* funNode, HandleAtom explicitName, FunctionFlags flags,
-      uint32_t toStringStart, InHandling inHandling,
+      FunctionNodeType* funNode, const ParserAtom* explicitName,
+      FunctionFlags flags, uint32_t toStringStart, InHandling inHandling,
       YieldHandling yieldHandling, FunctionSyntaxKind kind,
       GeneratorKind generatorKind, FunctionAsyncKind asyncKind, bool tryAnnexB,
       Directives inheritedDirectives, Directives* newDirectives);
 
   MOZ_MUST_USE bool advancePastSyntaxParsedFunction(AutoKeepAtoms& keepAtoms,
                                                     SyntaxParser* syntaxParser);
 
   bool skipLazyInnerFunction(FunctionNodeType funNode, uint32_t toStringStart,
@@ -1740,19 +1745,19 @@ class MOZ_STACK_CLASS Parser<FullParseHa
 
   bool checkStatementsEOF();
 
   // Parse the body of a global script.
   ListNodeType globalBody(GlobalSharedContext* globalsc);
 
   bool namedImportsOrNamespaceImport(TokenKind tt, ListNodeType importSpecSet);
 
-  PropertyName* importedBinding() { return bindingIdentifier(YieldIsName); }
+  const ParserName* importedBinding() { return bindingIdentifier(YieldIsName); }
 
-  bool checkLocalExportName(PropertyName* ident, uint32_t offset) {
+  bool checkLocalExportName(const ParserName* ident, uint32_t offset) {
     return checkLabelOrIdentifierReference(ident, offset, YieldIsName);
   }
 
   bool asmJS(ListNodeType list);
 };
 
 template <class Parser>
 /* static */ inline const TokenStreamAnyChars&
@@ -1839,43 +1844,48 @@ class MOZ_STACK_CLASS AutoInParametersOf
     parser_->setInParametersOfAsyncFunction(inParameters);
   }
 
   ~AutoInParametersOfAsyncFunction() {
     parser_->setInParametersOfAsyncFunction(oldInParametersOfAsyncFunction_);
   }
 };
 
-GlobalScope::Data* NewEmptyGlobalScopeData(JSContext* cx, LifoAlloc& alloc,
-                                           uint32_t numBindings);
-
-VarScope::Data* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc,
-                                     uint32_t numBindings);
-
-LexicalScope::Data* NewEmptyLexicalScopeData(JSContext* cx, LifoAlloc& alloc,
-                                             uint32_t numBindings);
-
-FunctionScope::Data* NewEmptyFunctionScopeData(JSContext* cx, LifoAlloc& alloc,
+ParserGlobalScopeData* NewEmptyGlobalScopeData(JSContext* cx, LifoAlloc& alloc,
                                                uint32_t numBindings);
 
-mozilla::Maybe<GlobalScope::Data*> NewGlobalScopeData(
+ParserVarScopeData* NewEmptyVarScopeData(JSContext* cx, LifoAlloc& alloc,
+                                         uint32_t numBindings);
+
+ParserLexicalScopeData* NewEmptyLexicalScopeData(JSContext* cx,
+                                                 LifoAlloc& alloc,
+                                                 uint32_t numBindings);
+
+ParserFunctionScopeData* NewEmptyFunctionScopeData(JSContext* cx,
+                                                   LifoAlloc& alloc,
+                                                   uint32_t numBindings);
+
+mozilla::Maybe<ParserGlobalScopeData*> NewGlobalScopeData(
     JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc,
     ParseContext* pc);
-mozilla::Maybe<EvalScope::Data*> NewEvalScopeData(JSContext* context,
-                                                  ParseContext::Scope& scope,
-                                                  LifoAlloc& alloc,
-                                                  ParseContext* pc);
-mozilla::Maybe<FunctionScope::Data*> NewFunctionScopeData(
+
+mozilla::Maybe<ParserEvalScopeData*> NewEvalScopeData(
+    JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc,
+    ParseContext* pc);
+
+mozilla::Maybe<ParserFunctionScopeData*> NewFunctionScopeData(
     JSContext* context, ParseContext::Scope& scope, bool hasParameterExprs,
     LifoAlloc& alloc, ParseContext* pc);
-mozilla::Maybe<VarScope::Data*> NewVarScopeData(JSContext* context,
-                                                ParseContext::Scope& scope,
-                                                LifoAlloc& alloc,
-                                                ParseContext* pc);
-mozilla::Maybe<LexicalScope::Data*> NewLexicalScopeData(
+
+mozilla::Maybe<ParserVarScopeData*> NewVarScopeData(JSContext* context,
+                                                    ParseContext::Scope& scope,
+                                                    LifoAlloc& alloc,
+                                                    ParseContext* pc);
+
+mozilla::Maybe<ParserLexicalScopeData*> NewLexicalScopeData(
     JSContext* context, ParseContext::Scope& scope, LifoAlloc& alloc,
     ParseContext* pc);
 
 bool FunctionScopeHasClosedOverBindings(ParseContext* pc);
 bool LexicalScopeHasClosedOverBindings(ParseContext* pc,
                                        ParseContext::Scope& scope);
 
 } /* namespace frontend */
--- a/js/src/frontend/ParserAtom.cpp
+++ b/js/src/frontend/ParserAtom.cpp
@@ -11,25 +11,16 @@
 #include "jsnum.h"
 
 #include "frontend/NameCollections.h"
 #include "vm/JSContext.h"
 #include "vm/Printer.h"
 #include "vm/Runtime.h"
 #include "vm/StringType.h"
 
-//
-// Parser-Atoms should be disabled for now.  This check ensures that.
-// NOTE: This will be removed when the final transition patches from
-//   JS-atoms to parser-atoms lands.
-//
-#ifdef JS_PARSER_ATOMS
-#  error "Parser atoms define should remain disabled until this is removed."
-#endif
-
 using namespace js;
 using namespace js::frontend;
 
 namespace js {
 namespace frontend {
 
 static JS::OOM PARSER_ATOMS_OOM;
 
@@ -330,39 +321,52 @@ static void FillChar16Buffer(char16_t* b
   if (ent->hasLatin1Chars()) {
     std::copy(ent->latin1Chars(), ent->latin1Chars() + ent->length(), buf);
   } else {
     std::copy(ent->twoByteChars(), ent->twoByteChars() + ent->length(), buf);
   }
 }
 
 JS::Result<const ParserAtom*, OOM&> ParserAtomsTable::concatAtoms(
-    JSContext* cx, const ParserAtom* prefix, const ParserAtom* suffix) {
-  bool latin1 = prefix->hasLatin1Chars() && suffix->hasLatin1Chars();
-  size_t prefixLength = prefix->length();
-  size_t suffixLength = suffix->length();
-  size_t catLen = prefixLength + suffixLength;
+    JSContext* cx, mozilla::Range<const ParserAtom*> atoms) {
+  bool latin1 = true;
+  uint32_t catLen = 0;
+  for (const ParserAtom* atom : atoms) {
+    if (!atom->hasLatin1Chars()) {
+      latin1 = false;
+    }
+    // Overflow check here, length
+    if (atom->length() >= (ParserAtomEntry::MAX_LENGTH - catLen)) {
+      return RaiseParserAtomsOOMError(cx);
+    }
+    catLen += atom->length();
+  }
 
   if (latin1) {
     if (catLen <= ParserAtomEntry::MaxInline<Latin1Char>()) {
       Latin1Char buf[ParserAtomEntry::MaxInline<Latin1Char>()];
-      mozilla::PodCopy(buf, prefix->latin1Chars(), prefixLength);
-      mozilla::PodCopy(buf + prefixLength, suffix->latin1Chars(), suffixLength);
-
+      size_t offset = 0;
+      for (const ParserAtom* atom : atoms) {
+        mozilla::PodCopy(buf + offset, atom->latin1Chars(), atom->length());
+        offset += atom->length();
+      }
       return internLatin1(cx, buf, catLen);
     }
 
     // Concatenate a latin1 string and add it to the table.
     UniqueLatin1Chars copy(cx->pod_malloc<Latin1Char>(catLen));
     if (!copy) {
       return RaiseParserAtomsOOMError(cx);
     }
-    mozilla::PodCopy(copy.get(), prefix->latin1Chars(), prefixLength);
-    mozilla::PodCopy(copy.get() + prefixLength, suffix->latin1Chars(),
-                     suffixLength);
+    size_t offset = 0;
+    for (const ParserAtom* atom : atoms) {
+      mozilla::PodCopy(copy.get() + offset, atom->latin1Chars(),
+                       atom->length());
+      offset += atom->length();
+    }
 
     InflatedChar16Sequence<Latin1Char> seq(copy.get(), catLen);
 
     // Check for well-known or existing.
     AddPtr addPtr = lookupForAdd(cx, seq);
     if (addPtr) {
       return addPtr.get()->asAtom();
     }
@@ -371,18 +375,21 @@ JS::Result<const ParserAtom*, OOM&> Pars
     UniquePtr<ParserAtomEntry> entry;
     MOZ_TRY_VAR(entry, ParserAtomEntry::allocate(cx, std::move(copy), catLen,
                                                  addPtr.inner().hash));
     return addEntry(cx, addPtr, std::move(entry));
   }
 
   if (catLen <= ParserAtomEntry::MaxInline<char16_t>()) {
     char16_t buf[ParserAtomEntry::MaxInline<char16_t>()];
-    FillChar16Buffer(buf, prefix);
-    FillChar16Buffer(buf + prefixLength, suffix);
+    size_t offset = 0;
+    for (const ParserAtom* atom : atoms) {
+      FillChar16Buffer(buf + offset, atom);
+      offset += atom->length();
+    }
 
     InflatedChar16Sequence<char16_t> seq(buf, catLen);
 
     // Check for well-known or existing.
     AddPtr addPtr = lookupForAdd(cx, seq);
     if (addPtr) {
       return addPtr.get()->asAtom();
     }
@@ -394,18 +401,21 @@ JS::Result<const ParserAtom*, OOM&> Pars
     return addEntry(cx, addPtr, std::move(entry));
   }
 
   // Concatenate a char16 string and add it to the table.
   UniqueTwoByteChars copy(cx->pod_malloc<char16_t>(catLen));
   if (!copy) {
     return RaiseParserAtomsOOMError(cx);
   }
-  FillChar16Buffer(copy.get(), prefix);
-  FillChar16Buffer(copy.get() + prefixLength, suffix);
+  size_t offset = 0;
+  for (const ParserAtom* atom : atoms) {
+    FillChar16Buffer(copy.get() + offset, atom);
+    offset += atom->length();
+  }
 
   InflatedChar16Sequence<char16_t> seq(copy.get(), catLen);
 
   // Check for well-known or existing.
   AddPtr addPtr = lookupForAdd(cx, seq);
   if (addPtr) {
     return addPtr.get()->asAtom();
   }
@@ -495,38 +505,30 @@ bool WellKnownParserAtoms::init(JSContex
 
   return true;
 }
 
 } /* namespace frontend */
 } /* namespace js */
 
 bool JSRuntime::initializeParserAtoms(JSContext* cx) {
-#ifdef JS_PARSER_ATOMS
   MOZ_ASSERT(!commonParserNames);
 
   if (parentRuntime) {
     commonParserNames = parentRuntime->commonParserNames;
     return true;
   }
 
   UniquePtr<js::frontend::WellKnownParserAtoms> names(
       js_new<js::frontend::WellKnownParserAtoms>(cx));
   if (!names || !names->init(cx)) {
     return false;
   }
 
   commonParserNames = names.release();
-#else
-  commonParserNames = nullptr;
-#endif  // JS_PARSER_ATOMS
   return true;
 }
 
 void JSRuntime::finishParserAtoms() {
-#ifdef JS_PARSER_ATOMS
   if (!parentRuntime) {
     js_delete(commonParserNames.ref());
   }
-#else
-  MOZ_ASSERT(!commonParserNames);
-#endif  // JS_PARSER_ATOMS
 }
--- a/js/src/frontend/ParserAtom.h
+++ b/js/src/frontend/ParserAtom.h
@@ -7,20 +7,21 @@
 #ifndef frontend_ParserAtom_h
 #define frontend_ParserAtom_h
 
 #include "mozilla/DebugOnly.h"      // mozilla::DebugOnly
 #include "mozilla/HashFunctions.h"  // HashString
 #include "mozilla/Range.h"          // mozilla::Range
 #include "mozilla/Variant.h"        // mozilla::Variant
 
-#include "ds/LifoAlloc.h"  // LifoAlloc
-#include "js/HashTable.h"  // HashSet
-#include "js/UniquePtr.h"  // js::UniquePtr
-#include "js/Vector.h"     // Vector
+#include "ds/LifoAlloc.h"    // LifoAlloc
+#include "js/GCPolicyAPI.h"  // JS::GCPolicy, JS::IgnoreGCPolicy
+#include "js/HashTable.h"    // HashSet
+#include "js/UniquePtr.h"    // js::UniquePtr
+#include "js/Vector.h"       // Vector
 #include "vm/CommonPropertyNames.h"
 #include "vm/StringType.h"  // CompareChars, StringEqualsAscii
 
 namespace js {
 namespace frontend {
 
 class ParserAtom;
 class ParserName;
@@ -174,17 +175,16 @@ class alignas(alignof(void*)) ParserAtom
         MOZ_ASSERT(ch <= MAX_LATIN1_CHAR);
       }
       MOZ_ASSERT(cur < (buf + length));
       *cur = ch;
       cur++;
     }
   }
 
- public:
  private:
   // Owned characters, either 8-bit Latin1Char, or 16-bit char16_t
   ContentPtrVariant variant_;
 
   // The length of the buffer in chars_.
   uint32_t length_;
 
   // The JSAtom-compatible hash of the string.
@@ -195,16 +195,18 @@ class alignas(alignof(void*)) ParserAtom
   // atom previously, the atom reference is kept here.
   //
   // Note: if/when this field is removed, remove the comment
   // in front of the call to `rt->initializeParserAtoms()` in
   // `JS::InitSelfHostedCode`.
   mutable JSAtom* jsatom_ = nullptr;
 
  public:
+  static const uint32_t MAX_LENGTH = JSString::MAX_LENGTH;
+
   template <typename CharT>
   ParserAtomEntry(mozilla::UniquePtr<CharT[], JS::FreePolicy> chars,
                   uint32_t length, HashNumber hash)
       : variant_(std::move(chars)), length_(length), hash_(hash) {}
 
   template <typename CharT>
   ParserAtomEntry(const CharT* chars, uint32_t length, HashNumber hash)
       : variant_(chars, /* isInline = */ true), length_(length), hash_(hash) {}
@@ -455,19 +457,18 @@ class ParserAtomsTable {
                                                    const Latin1Char* latin1Ptr,
                                                    uint32_t length);
 
   JS::Result<const ParserAtom*, OOM&> internUtf8(
       JSContext* cx, const mozilla::Utf8Unit* utf8Ptr, uint32_t length);
 
   JS::Result<const ParserAtom*, OOM&> internJSAtom(JSContext* cx, JSAtom* atom);
 
-  JS::Result<const ParserAtom*, OOM&> concatAtoms(JSContext* cx,
-                                                  const ParserAtom* prefix,
-                                                  const ParserAtom* suffix);
+  JS::Result<const ParserAtom*, OOM&> concatAtoms(
+      JSContext* cx, mozilla::Range<const ParserAtom*> atoms);
 };
 
 template <typename CharT>
 class SpecificParserAtomLookup : public ParserAtomLookup {
   // The sequence of characters to look up.
   InflatedChar16Sequence<CharT> seq_;
 
  public:
@@ -509,9 +510,16 @@ inline bool ParserAtomEntry::equalsSeq(
     }
   }
   return !seq.hasMore();
 }
 
 } /* namespace frontend */
 } /* namespace js */
 
+namespace JS {
+// Dummy trace policy until tracing is removed.
+template <>
+struct GCPolicy<const js::frontend::ParserAtom*>
+    : IgnoreGCPolicy<const js::frontend::ParserAtom*> {};
+}  // namespace JS
+
 #endif  // frontend_ParserAtom_h
--- a/js/src/frontend/PropOpEmitter.cpp
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -13,17 +13,17 @@
 #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(JSAtom* prop) {
+bool PropOpEmitter::prepareAtomIndex(const ParserAtom* prop) {
   if (!bce_->makeAtomIndex(prop, &propAtomIndex_)) {
     return false;
   }
   isLength_ = prop == bce_->cx->parserNames().length;
 
   return true;
 }
 
@@ -31,17 +31,17 @@ bool PropOpEmitter::prepareForObj() {
   MOZ_ASSERT(state_ == State::Start);
 
 #ifdef DEBUG
   state_ = State::Obj;
 #endif
   return true;
 }
 
-bool PropOpEmitter::emitGet(JSAtom* prop) {
+bool PropOpEmitter::emitGet(const ParserAtom* prop) {
   MOZ_ASSERT(state_ == State::Obj);
 
   if (!prepareAtomIndex(prop)) {
     return false;
   }
   if (isCall()) {
     if (!bce_->emit1(JSOp::Dup)) {
       //            [stack] # if Super
@@ -129,17 +129,17 @@ bool PropOpEmitter::skipObjAndRhs() {
   MOZ_ASSERT(isSimpleAssignment() || isPropInit());
 
 #ifdef DEBUG
   state_ = State::Rhs;
 #endif
   return true;
 }
 
-bool PropOpEmitter::emitDelete(JSAtom* prop) {
+bool PropOpEmitter::emitDelete(const ParserAtom* prop) {
   MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj);
   MOZ_ASSERT_IF(isSuper(), state_ == State::Start);
   MOZ_ASSERT(isDelete());
 
   if (!prepareAtomIndex(prop)) {
     return false;
   }
   if (isSuper()) {
@@ -169,17 +169,17 @@ bool PropOpEmitter::emitDelete(JSAtom* p
   }
 
 #ifdef DEBUG
   state_ = State::Delete;
 #endif
   return true;
 }
 
-bool PropOpEmitter::emitAssignment(JSAtom* prop) {
+bool PropOpEmitter::emitAssignment(const ParserAtom* prop) {
   MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment());
   MOZ_ASSERT(state_ == State::Rhs);
 
   if (isSimpleAssignment() || isPropInit()) {
     if (!prepareAtomIndex(prop)) {
       return false;
     }
   }
@@ -197,17 +197,17 @@ bool PropOpEmitter::emitAssignment(JSAto
   }
 
 #ifdef DEBUG
   state_ = State::Assignment;
 #endif
   return true;
 }
 
-bool PropOpEmitter::emitIncDec(JSAtom* prop) {
+bool PropOpEmitter::emitIncDec(const ParserAtom* prop) {
   MOZ_ASSERT(state_ == State::Obj);
   MOZ_ASSERT(isIncDec());
 
   if (!emitGet(prop)) {
     return false;
   }
 
   MOZ_ASSERT(state_ == State::Get);
--- a/js/src/frontend/PropOpEmitter.h
+++ b/js/src/frontend/PropOpEmitter.h
@@ -13,16 +13,17 @@
 
 #include "js/TypeDecls.h"
 #include "vm/SharedStencil.h"  // GCThingIndex
 
 namespace js {
 namespace frontend {
 
 struct BytecodeEmitter;
+class ParserAtom;
 
 // Class for emitting bytecode for property operation.
 //
 // Usage: (check for the return value is omitted for simplicity)
 //
 //   `obj.prop;`
 //     PropOpEmitter poe(this,
 //                       PropOpEmitter::Kind::Get,
@@ -227,30 +228,30 @@ class MOZ_STACK_CLASS PropOpEmitter {
   MOZ_MUST_USE bool isPreIncDec() const {
     return kind_ == Kind::PreIncrement || kind_ == Kind::PreDecrement;
   }
 
   MOZ_MUST_USE bool isInc() const {
     return kind_ == Kind::PostIncrement || kind_ == Kind::PreIncrement;
   }
 
-  MOZ_MUST_USE bool prepareAtomIndex(JSAtom* prop);
+  MOZ_MUST_USE bool prepareAtomIndex(const ParserAtom* prop);
 
  public:
   MOZ_MUST_USE bool prepareForObj();
 
-  MOZ_MUST_USE bool emitGet(JSAtom* prop);
+  MOZ_MUST_USE bool emitGet(const ParserAtom* prop);
 
   MOZ_MUST_USE bool prepareForRhs();
   MOZ_MUST_USE bool skipObjAndRhs();
 
-  MOZ_MUST_USE bool emitDelete(JSAtom* prop);
+  MOZ_MUST_USE bool emitDelete(const ParserAtom* prop);
 
   // `prop` can be nullptr for CompoundAssignment.
-  MOZ_MUST_USE bool emitAssignment(JSAtom* prop);
+  MOZ_MUST_USE bool emitAssignment(const ParserAtom* prop);
 
-  MOZ_MUST_USE bool emitIncDec(JSAtom* prop);
+  MOZ_MUST_USE bool emitIncDec(const ParserAtom* prop);
 };
 
 } /* namespace frontend */
 } /* namespace js */
 
 #endif /* frontend_PropOpEmitter_h */
--- a/js/src/frontend/SharedContext-inl.h
+++ b/js/src/frontend/SharedContext-inl.h
@@ -11,13 +11,20 @@
 #include "frontend/ParseContext.h"
 
 namespace js {
 namespace frontend {
 
 inline Directives::Directives(ParseContext* parent)
     : strict_(parent->sc()->strict()), asmJS_(parent->useAsmOrInsideUseAsm()) {}
 
+inline JSAtom* SharedContext::liftParserAtomToJSAtom(const ParserAtom* atomId) {
+  return compilationInfo_.liftParserAtomToJSAtom(atomId);
+}
+inline const ParserAtom* SharedContext::lowerJSAtomToParserAtom(JSAtom* atom) {
+  return compilationInfo_.lowerJSAtomToParserAtom(atom);
+}
+
 }  // namespace frontend
 
 }  // namespace js
 
 #endif  // frontend_SharedContext_inl_h
--- a/js/src/frontend/SharedContext.cpp
+++ b/js/src/frontend/SharedContext.cpp
@@ -200,34 +200,34 @@ void ScopeContext::computeExternalInitia
     }
   }
 }
 
 EvalSharedContext::EvalSharedContext(JSContext* cx,
                                      CompilationInfo& compilationInfo,
                                      Directives directives, SourceExtent extent)
     : SharedContext(cx, Kind::Eval, compilationInfo, directives, extent),
-      bindings(cx) {
+      bindings(nullptr) {
   // Eval inherits syntax and binding rules from enclosing environment.
   allowNewTarget_ = compilationInfo.scopeContext.allowNewTarget;
   allowSuperProperty_ = compilationInfo.scopeContext.allowSuperProperty;
   allowSuperCall_ = compilationInfo.scopeContext.allowSuperCall;
   allowArguments_ = compilationInfo.scopeContext.allowArguments;
   thisBinding_ = compilationInfo.scopeContext.thisBinding;
   inWith_ = compilationInfo.scopeContext.inWith;
 }
 
 #ifdef DEBUG
 bool FunctionBox::atomsAreKept() { return cx_->zone()->hasKeptAtoms(); }
 #endif
 
 FunctionBox::FunctionBox(JSContext* cx, FunctionBox* traceListHead,
                          SourceExtent extent, CompilationInfo& compilationInfo,
                          Directives directives, GeneratorKind generatorKind,
-                         FunctionAsyncKind asyncKind, JSAtom* atom,
+                         FunctionAsyncKind asyncKind, const ParserAtom* atom,
                          FunctionFlags flags, FunctionIndex index,
                          TopLevelFunction isTopLevel)
     : SharedContext(cx, Kind::FunctionBox, compilationInfo, directives, extent),
       traceLink_(traceListHead),
       atom_(atom),
       funcDataIndex_(index),
       flags_(flags),
       isTopLevel_(isTopLevel),
@@ -376,29 +376,25 @@ bool FunctionBox::setAsmJSModule(const J
 
 /* static */
 void FunctionBox::TraceList(JSTracer* trc, FunctionBox* listHead) {
   for (FunctionBox* node = listHead; node; node = node->traceLink_) {
     node->trace(trc);
   }
 }
 
-void FunctionBox::trace(JSTracer* trc) {
-  if (atom_) {
-    TraceRoot(trc, &atom_, "funbox-atom");
-  }
-}
+void FunctionBox::trace(JSTracer* trc) {}
 
 ModuleSharedContext::ModuleSharedContext(JSContext* cx,
                                          CompilationInfo& compilationInfo,
                                          ModuleBuilder& builder,
                                          SourceExtent extent)
     : SharedContext(cx, Kind::Module, compilationInfo, Directives(true),
                     extent),
-      bindings(cx),
+      bindings(nullptr),
       builder(builder) {
   thisBinding_ = ThisBinding::Module;
   setFlag(ImmutableFlags::HasModuleGoal);
 }
 
 MutableHandle<ScriptStencil> FunctionBox::functionStencil() const {
   if (isTopLevel_ == TopLevelFunction::Yes) {
     return &compilationInfo_.topLevel;
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -102,16 +102,31 @@ enum class ThisBinding : uint8_t {
   Function,
   DerivedConstructor
 };
 
 class GlobalSharedContext;
 class EvalSharedContext;
 class ModuleSharedContext;
 
+using ParserBindingName = AbstractBindingName<const ParserAtom>;
+using ParserBindingIter = AbstractBindingIter<const ParserAtom>;
+
+using BaseParserScopeData = AbstractBaseScopeData<const ParserAtom>;
+
+template <typename Scope>
+using ParserScopeData = typename Scope::template AbstractData<const ParserAtom>;
+
+using ParserGlobalScopeData = ParserScopeData<GlobalScope>;
+using ParserEvalScopeData = ParserScopeData<EvalScope>;
+using ParserLexicalScopeData = ParserScopeData<LexicalScope>;
+using ParserFunctionScopeData = ParserScopeData<FunctionScope>;
+using ParserModuleScopeData = ParserScopeData<ModuleScope>;
+using ParserVarScopeData = ParserScopeData<VarScope>;
+
 #define FLAG_GETTER(enumName, enumEntry, lowerName, name) \
  public:                                                  \
   bool lowerName() const { return hasFlag(enumName::enumEntry); }
 
 #define FLAG_SETTER(enumName, enumEntry, lowerName, name) \
  public:                                                  \
   void set##name() { setFlag(enumName::enumEntry); }      \
   void set##name(bool b) { setFlag(enumName::enumEntry, b); }
@@ -255,47 +270,50 @@ class SharedContext {
   bool strict() const { return hasFlag(ImmutableFlags::Strict) || localStrict; }
   void setStrictScript() { setFlag(ImmutableFlags::Strict); }
   bool setLocalStrictMode(bool strict) {
     bool retVal = localStrict;
     localStrict = strict;
     return retVal;
   }
 
+  inline JSAtom* liftParserAtomToJSAtom(const ParserAtom* atomId);
+  inline const ParserAtom* lowerJSAtomToParserAtom(JSAtom* atom);
+
   void copyScriptFields(ScriptStencil& stencil);
 };
 
 class MOZ_STACK_CLASS GlobalSharedContext : public SharedContext {
   ScopeKind scopeKind_;
 
  public:
-  Rooted<GlobalScope::Data*> bindings;
+  ParserGlobalScopeData* bindings;
 
   GlobalSharedContext(JSContext* cx, ScopeKind scopeKind,
                       CompilationInfo& compilationInfo, Directives directives,
                       SourceExtent extent)
       : SharedContext(cx, Kind::Global, compilationInfo, directives, extent),
         scopeKind_(scopeKind),
-        bindings(cx) {
+        bindings(nullptr) {
     MOZ_ASSERT(scopeKind == ScopeKind::Global ||
                scopeKind == ScopeKind::NonSyntactic);
     MOZ_ASSERT(thisBinding_ == ThisBinding::Global);
   }
 
   ScopeKind scopeKind() const { return scopeKind_; }
 };
 
 inline GlobalSharedContext* SharedContext::asGlobalContext() {
   MOZ_ASSERT(isGlobalContext());
   return static_cast<GlobalSharedContext*>(this);
 }
 
 class MOZ_STACK_CLASS EvalSharedContext : public SharedContext {
  public:
-  Rooted<EvalScope::Data*> bindings;
+  ParserEvalScopeData* bindings;
 
   EvalSharedContext(JSContext* cx, CompilationInfo& compilationInfo,
                     Directives directives, SourceExtent extent);
 };
 
 inline EvalSharedContext* SharedContext::asEvalContext() {
   MOZ_ASSERT(isEvalContext());
   return static_cast<EvalSharedContext*>(this);
@@ -318,30 +336,30 @@ class FunctionBox : public SharedContext
   // partially initialized enclosing scopes, so we must avoid storing the
   // scope in the BaseScript until compilation has completed
   // successfully.)
   // This is copied to ScriptStencil.
   // Any update after the copy should be synced to the ScriptStencil.
   mozilla::Maybe<ScopeIndex> enclosingScopeIndex_;
 
   // Names from the named lambda scope, if a named lambda.
-  LexicalScope::Data* namedLambdaBindings_ = nullptr;
+  ParserLexicalScopeData* namedLambdaBindings_ = nullptr;
 
   // Names from the function scope.
-  FunctionScope::Data* functionScopeBindings_ = nullptr;
+  ParserFunctionScopeData* functionScopeBindings_ = nullptr;
 
   // Names from the extra 'var' scope of the function, if the parameter list
   // has expressions.
-  VarScope::Data* extraVarScopeBindings_ = nullptr;
+  ParserVarScopeData* extraVarScopeBindings_ = nullptr;
 
   // The explicit or implicit name of the function. The FunctionFlags indicate
   // the kind of name.
   // This is copied to ScriptStencil.
   // Any update after the copy should be synced to the ScriptStencil.
-  JSAtom* atom_ = nullptr;
+  const ParserAtom* atom_ = nullptr;
 
   // Index into CompilationInfo::{funcData, functions}.
   FunctionIndex funcDataIndex_ = FunctionIndex(-1);
 
   // See: FunctionFlags
   // This is copied to ScriptStencil.
   // Any update after the copy should be synced to the ScriptStencil.
   FunctionFlags flags_ = {};
@@ -407,41 +425,40 @@ class FunctionBox : public SharedContext
   // ScriptStencil by copyUpdated* methods.
   bool isFunctionFieldCopiedToStencil : 1;
 
   // End of fields.
 
   FunctionBox(JSContext* cx, FunctionBox* traceListHead, SourceExtent extent,
               CompilationInfo& compilationInfo, Directives directives,
               GeneratorKind generatorKind, FunctionAsyncKind asyncKind,
-              JSAtom* explicitName, FunctionFlags flags, FunctionIndex index,
-              TopLevelFunction isTopLevel);
+              const ParserAtom* explicitName, FunctionFlags flags,
+              FunctionIndex index, TopLevelFunction isTopLevel);
 
   MutableHandle<ScriptStencil> functionStencil() const;
 
 #ifdef DEBUG
   bool atomsAreKept();
 #endif
 
-  MutableHandle<LexicalScope::Data*> namedLambdaBindings() {
-    MOZ_ASSERT(atomsAreKept());
-    return MutableHandle<LexicalScope::Data*>::fromMarkedLocation(
-        &namedLambdaBindings_);
+  ParserLexicalScopeData* namedLambdaBindings() { return namedLambdaBindings_; }
+  void setNamedLambdaBindings(ParserLexicalScopeData* bindings) {
+    namedLambdaBindings_ = bindings;
   }
 
-  MutableHandle<FunctionScope::Data*> functionScopeBindings() {
-    MOZ_ASSERT(atomsAreKept());
-    return MutableHandle<FunctionScope::Data*>::fromMarkedLocation(
-        &functionScopeBindings_);
+  ParserFunctionScopeData* functionScopeBindings() {
+    return functionScopeBindings_;
+  }
+  void setFunctionScopeBindings(ParserFunctionScopeData* bindings) {
+    functionScopeBindings_ = bindings;
   }
 
-  MutableHandle<VarScope::Data*> extraVarScopeBindings() {
-    MOZ_ASSERT(atomsAreKept());
-    return MutableHandle<VarScope::Data*>::fromMarkedLocation(
-        &extraVarScopeBindings_);
+  ParserVarScopeData* extraVarScopeBindings() { return extraVarScopeBindings_; }
+  void setExtraVarScopeBindings(ParserVarScopeData* bindings) {
+    extraVarScopeBindings_ = bindings;
   }
 
   void initFromLazyFunction(JSFunction* fun);
 
   void initStandalone(ScopeContext& scopeContext, FunctionFlags flags,
                       FunctionSyntaxKind kind);
 
   void initWithEnclosingParseContext(ParseContext* enclosing,
@@ -548,32 +565,32 @@ class FunctionBox : public SharedContext
 
   bool isInterpreted() const { return flags_.hasBaseScript(); }
 
   FunctionFlags::FunctionKind kind() { return flags_.kind(); }
 
   bool hasInferredName() const { return flags_.hasInferredName(); }
   bool hasGuessedAtom() const { return flags_.hasGuessedAtom(); }
 
-  JSAtom* displayAtom() const { return atom_; }
-  JSAtom* explicitName() const {
+  const ParserAtom* displayAtom() const { return atom_; }
+  const ParserAtom* explicitName() const {
     return (hasInferredName() || hasGuessedAtom()) ? nullptr : atom_;
   }
 
   // NOTE: We propagate to any existing functions for now. This handles both the
   // delazification case where functions already exist, and also handles
   // code-coverage which is not yet deferred.
-  void setInferredName(JSAtom* atom) {
+  void setInferredName(const ParserAtom* atom) {
     atom_ = atom;
     flags_.setInferredName();
     if (isFunctionFieldCopiedToStencil) {
       copyUpdatedAtomAndFlags();
     }
   }
-  void setGuessedAtom(JSAtom* atom) {
+  void setGuessedAtom(const ParserAtom* atom) {
     atom_ = atom;
     flags_.setGuessedAtom();
     if (isFunctionFieldCopiedToStencil) {
       copyUpdatedAtomAndFlags();
     }
   }
 
   void setAlwaysNeedsArgsObj() {
--- a/js/src/frontend/Stencil.cpp
+++ b/js/src/frontend/Stencil.cpp
@@ -115,56 +115,18 @@ Scope* ScopeStencil::createScope(JSConte
     case ScopeKind::WasmInstance: {
       MOZ_CRASH("Unexpected deferred type");
     }
   }
   return scope;
 }
 
 void ScopeStencil::trace(JSTracer* trc) {
-  // Trace Datas
-  if (data_) {
-    switch (kind()) {
-      case ScopeKind::Function: {
-        data<FunctionScope>().trace(trc);
-        break;
-      }
-      case ScopeKind::Lexical:
-      case ScopeKind::SimpleCatch:
-      case ScopeKind::Catch:
-      case ScopeKind::NamedLambda:
-      case ScopeKind::StrictNamedLambda:
-      case ScopeKind::FunctionLexical:
-      case ScopeKind::ClassBody: {
-        data<LexicalScope>().trace(trc);
-        break;
-      }
-      case ScopeKind::FunctionBodyVar: {
-        data<VarScope>().trace(trc);
-        break;
-      }
-      case ScopeKind::Global:
-      case ScopeKind::NonSyntactic: {
-        data<GlobalScope>().trace(trc);
-        break;
-      }
-      case ScopeKind::Eval:
-      case ScopeKind::StrictEval: {
-        data<EvalScope>().trace(trc);
-        break;
-      }
-      case ScopeKind::Module: {
-        data<ModuleScope>().trace(trc);
-        break;
-      }
-      case ScopeKind::With:
-      default:
-        MOZ_CRASH("Unexpected data type");
-    }
-  }
+  // NOTE: Scope::Data fields such as `canonicalFunction` are always nullptr
+  //       while owned by a ScopeStencil so no additional tracing is needed.
 }
 
 uint32_t ScopeStencil::nextFrameSlot() const {
   switch (kind()) {
     case ScopeKind::Function:
       return nextFrameSlot<FunctionScope>();
     case ScopeKind::FunctionBodyVar:
       return nextFrameSlot<VarScope>();
@@ -192,52 +154,27 @@ uint32_t ScopeStencil::nextFrameSlot() c
       MOZ_CRASH(
           "With, WasmInstance and WasmFunction Scopes don't get "
           "nextFrameSlot()");
       return 0;
   }
   MOZ_CRASH("Not an enclosing intra-frame scope");
 }
 
-void StencilModuleEntry::trace(JSTracer* trc) {
-  if (specifier) {
-    TraceManuallyBarrieredEdge(trc, &specifier, "module specifier");
-  }
-  if (localName) {
-    TraceManuallyBarrieredEdge(trc, &localName, "module local name");
-  }
-  if (importName) {
-    TraceManuallyBarrieredEdge(trc, &importName, "module import name");
-  }
-  if (exportName) {
-    TraceManuallyBarrieredEdge(trc, &exportName, "module export name");
-  }
-}
+void StencilModuleEntry::trace(JSTracer* trc) {}
 
 void StencilModuleMetadata::trace(JSTracer* trc) {
   requestedModules.trace(trc);
   importEntries.trace(trc);
   localExportEntries.trace(trc);
   indirectExportEntries.trace(trc);
   starExportEntries.trace(trc);
 }
 
-void ScriptStencil::trace(JSTracer* trc) {
-  for (ScriptThingVariant& thing : gcThings) {
-    if (thing.is<ScriptAtom>()) {
-      JSAtom* atom = thing.as<ScriptAtom>();
-      TraceRoot(trc, &atom, "script-atom");
-      MOZ_ASSERT(atom == thing.as<ScriptAtom>(), "Atoms should be unmovable");
-    }
-  }
-
-  if (functionAtom) {
-    TraceRoot(trc, &functionAtom, "script-atom");
-  }
-}
+void ScriptStencil::trace(JSTracer* trc) {}
 
 static bool CreateLazyScript(JSContext* cx, CompilationInfo& compilationInfo,
                              ScriptStencil& stencil, HandleFunction function) {
   const ScriptThingsVector& gcthings = stencil.gcThings;
 
   Rooted<BaseScript*> lazy(
       cx, BaseScript::CreateRawLazy(cx, gcthings.length(), function,
                                     compilationInfo.sourceObject,
@@ -278,17 +215,24 @@ static JSFunction* CreateFunction(JSCont
 
   gc::AllocKind allocKind = stencil.functionFlags.isExtended()
                                 ? gc::AllocKind::FUNCTION_EXTENDED
                                 : gc::AllocKind::FUNCTION;
   bool isAsmJS = stencil.functionFlags.isAsmJSNative();
 
   JSNative maybeNative = isAsmJS ? InstantiateAsmJS : nullptr;
 
-  RootedAtom displayAtom(cx, stencil.functionAtom);
+  RootedAtom displayAtom(cx);
+  if (stencil.functionAtom) {
+    displayAtom.set(
+        compilationInfo.liftParserAtomToJSAtom(stencil.functionAtom));
+    if (!displayAtom) {
+      return nullptr;
+    }
+  }
   RootedFunction fun(
       cx, NewFunctionWithProto(cx, maybeNative, stencil.nargs,
                                stencil.functionFlags, nullptr, displayAtom,
                                proto, allocKind, TenuredObject));
   if (!fun) {
     return nullptr;
   }
 
@@ -342,17 +286,17 @@ static bool MaybeInstantiateModule(JSCon
                                    CompilationInfo& compilationInfo) {
   if (compilationInfo.topLevel.get().isModule()) {
     compilationInfo.module = ModuleObject::create(cx);
     if (!compilationInfo.module) {
       return false;
     }
 
     if (!compilationInfo.moduleMetadata.get().initModule(
-            cx, compilationInfo.module)) {
+            cx, compilationInfo, compilationInfo.module)) {
       return false;
     }
   }
 
   return true;
 }
 
 // Instantiate JSFunctions for each FunctionBox.
@@ -510,17 +454,17 @@ static bool InstantiateTopLevel(JSContex
 
   return true;
 }
 
 // When a function is first referenced by enclosing script's bytecode, we need
 // to update it with information determined by the BytecodeEmitter. This applies
 // to both initial and delazification parses. The functions being update may or
 // may not have bytecode at this point.
-static void UpdateEmittedInnerFunctions(CompilationInfo& compilationInfo) {
+static bool UpdateEmittedInnerFunctions(CompilationInfo& compilationInfo) {
   for (auto item : compilationInfo.functionScriptStencils()) {
     auto& stencil = item.stencil;
     auto& fun = item.function;
     if (!stencil.wasFunctionEmitted) {
       continue;
     }
 
     if (stencil.functionFlags.isAsmJSNative() ||
@@ -540,25 +484,34 @@ static void UpdateEmittedInnerFunctions(
       if (stencil.memberInitializers) {
         script->setMemberInitializers(*stencil.memberInitializers);
       }
     }
 
     // Inferred and Guessed names are computed by BytecodeEmitter and so may
     // need to be applied to existing JSFunctions during delazification.
     if (fun->displayAtom() == nullptr) {
+      JSAtom* funcAtom = nullptr;
+      if (stencil.functionFlags.hasInferredName() ||
+          stencil.functionFlags.hasGuessedAtom()) {
+        funcAtom = compilationInfo.liftParserAtomToJSAtom(stencil.functionAtom);
+        if (!funcAtom) {
+          return false;
+        }
+      }
       if (stencil.functionFlags.hasInferredName()) {
-        fun->setInferredName(stencil.functionAtom);
+        fun->setInferredName(funcAtom);
       }
 
       if (stencil.functionFlags.hasGuessedAtom()) {
-        fun->setGuessedAtom(stencil.functionAtom);
+        fun->setGuessedAtom(funcAtom);
       }
     }
   }
+  return true;
 }
 
 // During initial parse we must link lazy-functions-inside-lazy-functions to
 // their enclosing script.
 static void LinkEnclosingLazyScript(CompilationInfo& compilationInfo) {
   for (auto item : compilationInfo.functionScriptStencils()) {
     auto& stencil = item.stencil;
     auto& fun = item.function;
@@ -635,17 +588,19 @@ bool CompilationInfo::instantiateStencil
   }
 
   if (!InstantiateTopLevel(cx, *this)) {
     return false;
   }
 
   // Must be infallible from here forward.
 
-  UpdateEmittedInnerFunctions(*this);
+  if (!UpdateEmittedInnerFunctions(*this)) {
+    return false;
+  }
 
   if (lazy == nullptr) {
     LinkEnclosingLazyScript(*this);
   }
 
   return true;
 }
 
@@ -754,106 +709,110 @@ void ScopeStencil::dumpFields(js::JSONPr
       json.property("functionIndex", "Nothing");
     }
 
     json.boolProperty("isArrow", isArrow_);
   }
 
   json.beginObjectProperty("data");
 
-  AbstractTrailingNamesArray<JSAtom>* trailingNames = nullptr;
+  AbstractTrailingNamesArray<const ParserAtom>* trailingNames = nullptr;
   uint32_t length = 0;
 
   switch (kind_) {
     case ScopeKind::Function: {
-      auto* data = static_cast<FunctionScope::Data*>(data_.get());
+      auto* data = static_cast<ParserFunctionScopeData*>(data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
       json.property("hasParameterExprs", data->hasParameterExprs);
       json.property("nonPositionalFormalStart", data->nonPositionalFormalStart);
       json.property("varStart", data->varStart);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::FunctionBodyVar: {
-      auto* data = static_cast<VarScope::Data*>(data_.get());
+      auto* data = static_cast<ParserVarScopeData*>(data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::Lexical:
     case ScopeKind::SimpleCatch:
     case ScopeKind::Catch:
     case ScopeKind::NamedLambda:
     case ScopeKind::StrictNamedLambda:
     case ScopeKind::FunctionLexical:
     case ScopeKind::ClassBody: {
-      auto* data = static_cast<LexicalScope::Data*>(data_.get());
+      auto* data = static_cast<ParserLexicalScopeData*>(data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
       json.property("constStart", data->constStart);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::With: {
       break;
     }
 
     case ScopeKind::Eval:
     case ScopeKind::StrictEval: {
-      auto* data = static_cast<EvalScope::Data*>(data_.get());
+      auto* data = static_cast<ParserEvalScopeData*>(data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::Global:
     case ScopeKind::NonSyntactic: {
-      auto* data = static_cast<GlobalScope::Data*>(data_.get());
+      auto* data = static_cast<ParserGlobalScopeData*>(data_);
       json.property("letStart", data->letStart);
       json.property("constStart", data->constStart);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::Module: {
-      auto* data = static_cast<ModuleScope::Data*>(data_.get());
+      auto* data = static_cast<ParserModuleScopeData*>(data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
       json.property("varStart", data->varStart);
       json.property("letStart", data->letStart);
       json.property("constStart", data->constStart);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::WasmInstance: {
-      auto* data = static_cast<WasmInstanceScope::Data*>(data_.get());
+      auto* data =
+          static_cast<AbstractScopeData<WasmInstanceScope, const ParserAtom>*>(
+              data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
       json.property("globalsStart", data->globalsStart);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     case ScopeKind::WasmFunction: {
-      auto* data = static_cast<WasmFunctionScope::Data*>(data_.get());
+      auto* data =
+          static_cast<AbstractScopeData<WasmFunctionScope, const ParserAtom>*>(
+              data_);
       json.property("nextFrameSlot", data->nextFrameSlot);
 
       trailingNames = &data->trailingNames;
       length = data->length;
       break;
     }
 
     default: {
@@ -1138,17 +1097,17 @@ static void DumpFunctionFlagsItems(js::J
 
 static void DumpScriptThing(js::JSONPrinter& json, ScriptThingVariant& thing) {
   struct Matcher {
     js::JSONPrinter& json;
 
     void operator()(ScriptAtom& data) {
       json.beginObject();
       json.property("type", "ScriptAtom");
-      JSAtom* atom = data;
+      const ParserAtom* atom = data;
       GenericPrinter& out = json.beginStringProperty("value");
       atom->dumpCharsNoQuote(out);
       json.endStringProperty();
       json.endObject();
     }
 
     void operator()(NullScriptThing& data) { json.nullValue(); }
 
--- a/js/src/frontend/Stencil.h
+++ b/js/src/frontend/Stencil.h
@@ -16,17 +16,17 @@
 
 #include "frontend/AbstractScopePtr.h"    // AbstractScopePtr, ScopeIndex
 #include "frontend/FunctionSyntaxKind.h"  // FunctionSyntaxKind
 #include "frontend/ObjLiteral.h"          // ObjLiteralStencil
 #include "frontend/TypedIndex.h"          // TypedIndex
 #include "js/GCVariant.h"                 // GC Support for mozilla::Variant
 #include "js/RegExpFlags.h"               // JS::RegExpFlags
 #include "js/RootingAPI.h"                // Handle
-#include "js/TypeDecls.h"                 // JSContext,JSAtom,JSFunction
+#include "js/TypeDecls.h"                 // JSContext
 #include "js/UniquePtr.h"                 // js::UniquePtr
 #include "js/Utility.h"                   // UniqueTwoByteChars
 #include "js/Vector.h"                    // js::Vector
 #include "util/Text.h"                    // DuplicateString
 #include "vm/BigIntType.h"                // ParseBigIntLiteral
 #include "vm/FunctionFlags.h"             // FunctionFlags
 #include "vm/GeneratorAndAsyncKind.h"     // GeneratorKind, FunctionAsyncKind
 #include "vm/JSScript.h"                  // MemberInitializers
@@ -43,16 +43,29 @@ class JSONPrinter;
 
 namespace frontend {
 
 struct CompilationInfo;
 class ScriptStencil;
 class RegExpStencil;
 class BigIntStencil;
 
+using BaseParserScopeData = AbstractBaseScopeData<const ParserAtom>;
+
+template <typename Scope>
+using ParserScopeData = typename Scope::template AbstractData<const ParserAtom>;
+using ParserGlobalScopeData = ParserScopeData<GlobalScope>;
+using ParserEvalScopeData = ParserScopeData<EvalScope>;
+using ParserLexicalScopeData = ParserScopeData<LexicalScope>;
+using ParserFunctionScopeData = ParserScopeData<FunctionScope>;
+using ParserModuleScopeData = ParserScopeData<ModuleScope>;
+using ParserVarScopeData = ParserScopeData<VarScope>;
+
+using ParserBindingIter = AbstractBindingIter<const ParserAtom>;
+
 // [SMDOC] Script Stencil (Frontend Representation)
 //
 // Stencils are GC object free representations of artifacts created during
 // parsing and bytecode emission that are being used as part of Project
 // Stencil (https://bugzilla.mozilla.org/show_bug.cgi?id=stencil) to revamp
 // the frontend.
 //
 // Renaming to use the term stencil more broadly is still in progress.
@@ -159,66 +172,69 @@ class ScopeStencil {
   mozilla::Maybe<FunctionIndex> functionIndex_;
 
   // True if this is a FunctionScope for an arrow function.
   bool isArrow_;
 
   // The list of binding and scope-specific data. Note that the back pointers to
   // the owning JSFunction / ModuleObject are not set until Stencils are
   // converted to GC allocations.
-  UniquePtr<BaseScopeData> data_;
+  //
+  // !NOTE! This data is currently allocated on the CompilationInfo::lifoAlloc.
+  // TODO-Stencil: This must outlive the parse for stencil to be useful.
+  BaseParserScopeData* data_;
 
  public:
   ScopeStencil(ScopeKind kind, mozilla::Maybe<ScopeIndex> enclosing,
                uint32_t firstFrameSlot,
                mozilla::Maybe<uint32_t> numEnvironmentSlots,
-               UniquePtr<BaseScopeData> data = {},
+               BaseParserScopeData* data = {},
                mozilla::Maybe<FunctionIndex> functionIndex = mozilla::Nothing(),
                bool isArrow = false)
       : enclosing_(enclosing),
         kind_(kind),
         firstFrameSlot_(firstFrameSlot),
         numEnvironmentSlots_(numEnvironmentSlots),
         functionIndex_(functionIndex),
         isArrow_(isArrow),
-        data_(std::move(data)) {}
+        data_(data) {}
 
   static bool createForFunctionScope(
       JSContext* cx, CompilationInfo& compilationInfo,
-      Handle<FunctionScope::Data*> dataArg, bool hasParameterExprs,
+      ParserFunctionScopeData* dataArg, bool hasParameterExprs,
       bool needsEnvironment, FunctionIndex functionIndex, bool isArrow,
       mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);
 
   static bool createForLexicalScope(
       JSContext* cx, CompilationInfo& compilationInfo, ScopeKind kind,
-      Handle<LexicalScope::Data*> dataArg, uint32_t firstFrameSlot,
+      ParserLexicalScopeData* dataArg, uint32_t firstFrameSlot,
       mozilla::Maybe<ScopeIndex> enclosing, ScopeIndex* index);
 
-  static bool createForVarScope(JSContext* cx, CompilationInfo& compilationInfo,
-                                ScopeKind kind, Handle<VarScope::Data*> dataArg,
+  static bool createForVarScope(JSContext* cx,
+                                frontend::CompilationInfo& compilationInfo,
+                                ScopeKind kind, ParserVarScopeData* dataArg,
                                 uint32_t firstFrameSlot, bool needsEnvironment,
                                 mozilla::Maybe<ScopeIndex> enclosing,
                                 ScopeIndex* index);
 
   static bool createForGlobalScope(JSContext* cx,
                                    CompilationInfo& compilationInfo,
                                    ScopeKind kind,
-                                   Handle<GlobalScope::Data*> dataArg,
+                                   ParserGlobalScopeData* dataArg,
                                    ScopeIndex* index);
 
   static bool createForEvalScope(JSContext* cx,
                                  CompilationInfo& compilationInfo,
-                                 ScopeKind kind,
-                                 Handle<EvalScope::Data*> dataArg,
+                                 ScopeKind kind, ParserEvalScopeData* dataArg,
                                  mozilla::Maybe<ScopeIndex> enclosing,
                                  ScopeIndex* index);
 
   static bool createForModuleScope(JSContext* cx,
                                    CompilationInfo& compilationInfo,
-                                   Handle<ModuleScope::Data*> dataArg,
+                                   ParserModuleScopeData* dataArg,
                                    mozilla::Maybe<ScopeIndex> enclosing,
                                    ScopeIndex* index);
 
   static bool createForWithScope(JSContext* cx,
                                  CompilationInfo& compilationInfo,
                                  mozilla::Maybe<ScopeIndex> enclosing,
                                  ScopeIndex* index);
 
@@ -245,25 +261,29 @@ class ScopeStencil {
   void dump();
   void dump(JSONPrinter& json);
   void dumpFields(JSONPrinter& json);
 #endif
 
  private:
   // Non owning reference to data
   template <typename SpecificScopeType>
-  typename SpecificScopeType::Data& data() const {
-    MOZ_ASSERT(data_.get());
-    return *static_cast<typename SpecificScopeType::Data*>(data_.get());
+  typename SpecificScopeType::template AbstractData<const ParserAtom>& data()
+      const {
+    using Data =
+        typename SpecificScopeType ::template AbstractData<const ParserAtom>;
+
+    MOZ_ASSERT(data_);
+    return *static_cast<Data*>(data_);
   }
 
   // Transfer ownership into a new UniquePtr.
   template <typename SpecificScopeType>
-  UniquePtr<typename SpecificScopeType::Data> releaseData(
-      CompilationInfo& compilationInfo);
+  UniquePtr<typename SpecificScopeType::Data> createSpecificScopeData(
+      JSContext* cx, CompilationInfo& compilationInfo);
 
   template <typename SpecificScopeType>
   uint32_t nextFrameSlot() const {
     // If a scope has been allocated for the ScopeStencil we no longer own data,
     // so defer to scope
     return data<SpecificScopeType>().nextFrameSlot;
   }
 
@@ -297,65 +317,67 @@ using FunctionDeclarationVector = Vector
 class StencilModuleEntry {
  public:
   //              | ModuleRequest | ImportEntry | ExportAs | ExportFrom |
   //              |-----------------------------------------------------|
   // specifier    | required      | required    | nullptr  | required   |
   // localName    | null          | required    | required | nullptr    |
   // importName   | null          | required    | nullptr  | required   |
   // exportName   | null          | null        | required | optional   |
-  JSAtom* specifier = nullptr;
-  JSAtom* localName = nullptr;
-  JSAtom* importName = nullptr;
-  JSAtom* exportName = nullptr;
+  const ParserAtom* specifier = nullptr;
+  const ParserAtom* localName = nullptr;
+  const ParserAtom* importName = nullptr;
+  const ParserAtom* exportName = nullptr;
 
   // Location used for error messages. If this is for a module request entry
   // then it is the module specifier string, otherwise the import/export spec
   // that failed. Exports may not fill these fields if an error cannot be
   // generated such as `export let x;`.
   uint32_t lineno = 0;
   uint32_t column = 0;
 
  private:
   StencilModuleEntry(uint32_t lineno, uint32_t column)
       : lineno(lineno), column(column) {}
 
  public:
-  static StencilModuleEntry moduleRequest(JSAtom* specifier, uint32_t lineno,
-                                          uint32_t column) {
+  static StencilModuleEntry moduleRequest(const ParserAtom* specifier,
+                                          uint32_t lineno, uint32_t column) {
     MOZ_ASSERT(specifier);
     StencilModuleEntry entry(lineno, column);
     entry.specifier = specifier;
     return entry;
   }
 
-  static StencilModuleEntry importEntry(JSAtom* specifier, JSAtom* localName,
-                                        JSAtom* importName, uint32_t lineno,
-                                        uint32_t column) {
+  static StencilModuleEntry importEntry(const ParserAtom* specifier,
+                                        const ParserAtom* localName,
+                                        const ParserAtom* importName,
+                                        uint32_t lineno, uint32_t column) {
     MOZ_ASSERT(specifier && localName && importName);
     StencilModuleEntry entry(lineno, column);
     entry.specifier = specifier;
     entry.localName = localName;
     entry.importName = importName;
     return entry;
   }
 
-  static StencilModuleEntry exportAsEntry(JSAtom* localName, JSAtom* exportName,
+  static StencilModuleEntry exportAsEntry(const ParserAtom* localName,
+                                          const ParserAtom* exportName,
                                           uint32_t lineno, uint32_t column) {
     MOZ_ASSERT(localName && exportName);
     StencilModuleEntry entry(lineno, column);
     entry.localName = localName;
     entry.exportName = exportName;
     return entry;
   }
 
-  static StencilModuleEntry exportFromEntry(JSAtom* specifier,
-                                            JSAtom* importName,
-                                            JSAtom* exportName, uint32_t lineno,
-                                            uint32_t column) {
+  static StencilModuleEntry exportFromEntry(const ParserAtom* specifier,
+                                            const ParserAtom* importName,
+                                            const ParserAtom* exportName,
+                                            uint32_t lineno, uint32_t column) {
     // NOTE: The `export * from "mod";` syntax generates nullptr exportName.
     MOZ_ASSERT(specifier && importName);
     StencilModuleEntry entry(lineno, column);
     entry.specifier = specifier;
     entry.importName = importName;
     entry.exportName = exportName;
     return entry;
   }
@@ -380,31 +402,33 @@ class StencilModuleMetadata {
   explicit StencilModuleMetadata(JSContext* cx)
       : requestedModules(cx),
         importEntries(cx),
         localExportEntries(cx),
         indirectExportEntries(cx),
         starExportEntries(cx),
         functionDecls(cx) {}
 
-  bool initModule(JSContext* cx, JS::Handle<ModuleObject*> module);
+  bool initModule(JSContext* cx, CompilationInfo& compilationInfo,
+                  JS::Handle<ModuleObject*> module);
 
   void trace(JSTracer* trc);
 
 #if defined(DEBUG) || defined(JS_JITSPEW)
   void dump();
   void dump(JSONPrinter& json);
   void dumpFields(JSONPrinter& json);
 #endif
 };
 
 // The lazy closed-over-binding info is represented by these types that will
-// convert to a GCCellPtr(nullptr), GCCellPtr(JSAtom*).
+// convert to a nullptr.
 class NullScriptThing {};
-using ScriptAtom = JSAtom*;
+
+using ScriptAtom = const ParserAtom*;
 
 // These types all end up being baked into GC things as part of stencil
 // instantiation.
 using ScriptThingVariant =
     mozilla::Variant<ScriptAtom, NullScriptThing, BigIntIndex, ObjLiteralIndex,
                      RegExpIndex, ScopeIndex, FunctionIndex,
                      EmptyGlobalScopeType>;
 
@@ -438,17 +462,17 @@ class ScriptStencil {
   // Fields for JSFunction.
   // Used by:
   //   * non-lazy Function
   //   * lazy Function
   //   * asm.js module
 
   // The explicit or implicit name of the function. The FunctionFlags indicate
   // the kind of name.
-  JSAtom* functionAtom = nullptr;
+  const ParserAtom* functionAtom = nullptr;
 
   // See: `FunctionFlags`.
   FunctionFlags functionFlags = {};
 
   // See `JSFunction::nargs_`.
   uint16_t nargs = 0;
 
   // If this ScriptStencil refers to a lazy child of the function being
--- a/js/src/frontend/SwitchEmitter.cpp
+++ b/js/src/frontend/SwitchEmitter.cpp
@@ -117,17 +117,17 @@ bool SwitchEmitter::emitDiscriminant(con
       return false;
     }
   }
 
   state_ = State::Discriminant;
   return true;
 }
 
-bool SwitchEmitter::emitLexical(Handle<LexicalScope::Data*> bindings) {
+bool SwitchEmitter::emitLexical(ParserLexicalScopeData* bindings) {
   MOZ_ASSERT(state_ == State::Discriminant);
   MOZ_ASSERT(bindings);
 
   tdzCacheLexical_.emplace(bce_);
   emitterScope_.emplace(bce_);
   if (!emitterScope_->enterLexical(bce_, ScopeKind::Lexical, bindings)) {
     return false;
   }
--- a/js/src/frontend/SwitchEmitter.h
+++ b/js/src/frontend/SwitchEmitter.h
@@ -436,17 +436,17 @@ class MOZ_STACK_CLASS SwitchEmitter {
   MOZ_MUST_USE bool emitDiscriminant(const mozilla::Maybe<uint32_t>& switchPos);
 
   // `caseCount` should be the number of cases in the switch statement,
   // excluding the default case.
   MOZ_MUST_USE bool validateCaseCount(uint32_t caseCount);
 
   // `bindings` is a lexical scope for the entire switch, in case there's
   // let/const effectively directly under case or default blocks.
-  MOZ_MUST_USE bool emitLexical(Handle<LexicalScope::Data*> bindings);
+  MOZ_MUST_USE bool emitLexical(ParserLexicalScopeData* bindings);
 
   MOZ_MUST_USE bool emitCond();
   MOZ_MUST_USE bool emitTable(const TableGenerator& tableGen);
 
   MOZ_MUST_USE bool prepareForCaseValue();
   MOZ_MUST_USE bool emitCaseJump();
 
   MOZ_MUST_USE bool emitCaseBody();
--- a/js/src/frontend/SyntaxParseHandler.h
+++ b/js/src/frontend/SyntaxParseHandler.h
@@ -31,17 +31,17 @@ namespace frontend {
 // When parsing, we start at the top level with a full parse, and when possible
 // only check the syntax for inner functions, so that they can be lazily parsed
 // into bytecode when/if they first run. Checking the syntax of a function is
 // several times faster than doing a full parse/emit, and lazy parsing improves
 // both performance and memory usage significantly when pages contain large
 // amounts of code that never executes (which happens often).
 class SyntaxParseHandler {
   // Remember the last encountered name or string literal during syntax parses.
-  JSAtom* lastAtom;
+  const ParserAtom* lastAtom;
   TokenPos lastStringPos;
 
   // WARNING: Be careful about adding fields to this function, that might be
   //          GC things (like JSAtom*).  The JS_HAZ_ROOTED causes the GC
   //          analysis to *ignore* anything that might be a rooting hazard in
   //          this class.  The |lastAtom| field above is safe because