Bug 1645845 - Add ParserAtomsTable, parser atoms types, common parser names table, and base parser atoms implementation. r=mgaudet,tcampbell
☠☠ backed out by 2463f34f9019 ☠ ☠
authorKannan Vijayan <kvijayan@mozilla.com>
Tue, 16 Jun 2020 22:16:17 +0000
changeset 535976 647adc688cea6d02996a575901f30316ec852edf
parent 535975 7f701cee12e73ae272232b153ea2ffc35d4e96ac
child 535977 2463f34f9019db5cfcf4b9ca4c10501fe699a440
push id37514
push userncsoregi@mozilla.com
push dateWed, 17 Jun 2020 09:36:37 +0000
treeherdermozilla-central@3155ffead6ae [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmgaudet, tcampbell
bugs1645845
milestone79.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 1645845 - Add ParserAtomsTable, parser atoms types, common parser names table, and base parser atoms implementation. r=mgaudet,tcampbell Differential Revision: https://phabricator.services.mozilla.com/D79714
js/moz.configure
js/public/Utility.h
js/src/NamespaceImports.h
js/src/frontend/CompilationInfo.h
js/src/frontend/ParserAtom.cpp
js/src/frontend/ParserAtom.h
js/src/frontend/TokenStream.cpp
js/src/frontend/TokenStream.h
js/src/frontend/moz.build
js/src/jsapi.cpp
js/src/jsnum.cpp
js/src/jsnum.h
js/src/util/Text.cpp
js/src/util/Text.h
js/src/vm/JSContext.h
js/src/vm/Runtime.h
js/src/vm/StringType.cpp
js/src/vm/StringType.h
--- a/js/moz.configure
+++ b/js/moz.configure
@@ -57,16 +57,22 @@ 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/public/Utility.h
+++ b/js/public/Utility.h
@@ -16,16 +16,17 @@
 
 #include <stdlib.h>
 #include <string.h>
 #include <type_traits>
 #include <utility>
 
 #include "jstypes.h"
 #include "mozmemory.h"
+#include "js/TypeDecls.h"
 
 /* The public JS engine namespace. */
 namespace JS {}
 
 /* The mozilla-shared reusable template/utility namespace. */
 namespace mozilla {}
 
 /* The private JS engine namespace. */
@@ -645,16 +646,17 @@ struct DeletePolicy {
 };
 
 struct FreePolicy {
   void operator()(const void* ptr) { js_free(const_cast<void*>(ptr)); }
 };
 
 typedef mozilla::UniquePtr<char[], JS::FreePolicy> UniqueChars;
 typedef mozilla::UniquePtr<char16_t[], JS::FreePolicy> UniqueTwoByteChars;
+typedef mozilla::UniquePtr<JS::Latin1Char[], JS::FreePolicy> UniqueLatin1Chars;
 
 }  // namespace JS
 
 /* sixgill annotation defines */
 #ifndef HAVE_STATIC_ANNOTATIONS
 #  define HAVE_STATIC_ANNOTATIONS
 #  ifdef XGILL_PLUGIN
 #    define STATIC_PRECONDITION(COND) __attribute__((precondition(#    COND)))
--- a/js/src/NamespaceImports.h
+++ b/js/src/NamespaceImports.h
@@ -69,16 +69,17 @@ using JS::ValueType;
 
 using JS::ConstTwoByteChars;
 using JS::Latin1Char;
 using JS::Latin1Chars;
 using JS::Latin1CharsZ;
 using JS::TwoByteChars;
 using JS::TwoByteCharsZ;
 using JS::UniqueChars;
+using JS::UniqueLatin1Chars;
 using JS::UniqueTwoByteChars;
 using JS::UTF8Chars;
 using JS::UTF8CharsZ;
 using JS::WTF8Chars;
 
 using JS::Ok;
 using JS::OOM;
 using JS::Result;
--- a/js/src/frontend/CompilationInfo.h
+++ b/js/src/frontend/CompilationInfo.h
@@ -7,16 +7,17 @@
 #ifndef frontend_CompilationInfo_h
 #define frontend_CompilationInfo_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/Variant.h"
 
 #include "ds/LifoAlloc.h"
+#include "frontend/ParserAtom.h"
 #include "frontend/SharedContext.h"
 #include "frontend/Stencil.h"
 #include "frontend/UsedNameTracker.h"
 #include "js/GCVariant.h"
 #include "js/GCVector.h"
 #include "js/HashTable.h"
 #include "js/RealmOptions.h"
 #include "js/SourceText.h"
@@ -70,16 +71,19 @@ struct ScopeContext {
 struct MOZ_RAII CompilationInfo : public JS::CustomAutoRooter {
   JSContext* cx;
   const JS::ReadOnlyCompileOptions& options;
 
   // Until we have dealt with Atoms in the front end, we need to hold
   // onto them.
   AutoKeepAtoms keepAtoms;
 
+  // Table of parser atoms for this compilation.
+  ParserAtomsTable parserAtoms;
+
   Directives directives;
 
   ScopeContext scopeContext;
 
   // List of function contexts for GC tracing. These are allocated in the
   // LifoAlloc and still require tracing.
   FunctionBox* traceListHead = nullptr;
 
@@ -137,16 +141,17 @@ struct MOZ_RAII CompilationInfo : public
   CompilationInfo(JSContext* cx, LifoAllocScope& alloc,
                   const JS::ReadOnlyCompileOptions& options,
                   Scope* enclosingScope = nullptr,
                   JSObject* enclosingEnv = nullptr)
       : JS::CustomAutoRooter(cx),
         cx(cx),
         options(options),
         keepAtoms(cx),
+        parserAtoms(cx),
         directives(options.forceStrictMode()),
         scopeContext(enclosingScope, enclosingEnv),
         script(cx),
         lazy(cx),
         usedNames(cx),
         allocScope(alloc),
         regExpData(cx),
         bigIntData(cx),
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ParserAtom.cpp
@@ -0,0 +1,440 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <type_traits>
+
+#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;
+
+mozilla::GenericErrorResult<OOM&> RaiseParserAtomsOOMError(JSContext* cx) {
+  js::ReportOutOfMemory(cx);
+  return mozilla::Err(PARSER_ATOMS_OOM);
+}
+
+bool ParserAtomEntry::equalsJSAtom(JSAtom* other) const {
+  // Compare hashes first.
+  if (hash_ != other->hash()) {
+    return false;
+  }
+  if (length_ != other->length()) {
+    return false;
+  }
+
+  JS::AutoCheckCannotGC nogc;
+
+  if (hasTwoByteChars()) {
+    // Compare heap-allocated 16-bit chars to atom.
+    return other->hasLatin1Chars()
+               ? EqualChars(twoByteChars(), other->latin1Chars(nogc), length_)
+               : EqualChars(twoByteChars(), other->twoByteChars(nogc), length_);
+  }
+
+  MOZ_ASSERT(hasLatin1Chars());
+  return other->hasLatin1Chars()
+             ? EqualChars(latin1Chars(), other->latin1Chars(nogc), length_)
+             : EqualChars(latin1Chars(), other->twoByteChars(nogc), length_);
+}
+
+UniqueChars ParserAtomToPrintableString(JSContext* cx, ParserAtomId atom) {
+  const ParserAtomEntry* entry = atom.entry();
+  Sprinter sprinter(cx);
+  if (!sprinter.init()) {
+    return nullptr;
+  }
+  size_t length = entry->length();
+  if (entry->hasLatin1Chars()) {
+    if (!QuoteString<QuoteTarget::String>(
+            &sprinter, mozilla::Range(entry->latin1Chars(), length))) {
+      return nullptr;
+    }
+  } else {
+    if (!QuoteString<QuoteTarget::String>(
+            &sprinter, mozilla::Range(entry->twoByteChars(), length))) {
+      return nullptr;
+    }
+  }
+  return sprinter.release();
+}
+
+bool ParserAtomEntry::isIndex(uint32_t* indexp) const {
+  if (hasLatin1Chars()) {
+    return js::CheckStringIsIndex(latin1Chars(), length(), indexp);
+  }
+  return js::CheckStringIsIndex(twoByteChars(), length(), indexp);
+}
+
+JS::Result<JSAtom*, OOM&> ParserAtomEntry::toJSAtom(JSContext* cx) const {
+  if (jsatom_) {
+    return jsatom_;
+  }
+
+  if (hasLatin1Chars()) {
+    jsatom_ = AtomizeChars(cx, latin1Chars(), length());
+  } else {
+    jsatom_ = AtomizeChars(cx, twoByteChars(), length());
+  }
+  if (!jsatom_) {
+    return RaiseParserAtomsOOMError(cx);
+  }
+  return jsatom_;
+}
+
+bool ParserAtomEntry::toNumber(JSContext* cx, double* result) const {
+  return hasLatin1Chars() ? CharsToNumber(cx, latin1Chars(), length(), result)
+                          : CharsToNumber(cx, twoByteChars(), length(), result);
+}
+
+ParserAtomsTable::ParserAtomsTable(JSContext* cx)
+    : entrySet_(cx), wellKnownTable_(*cx->runtime()->commonParserNames) {}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::addEntry(
+    JSContext* cx, EntrySet::AddPtr addPtr, ParserAtomEntry&& entry) {
+  UniquePtr<ParserAtomEntry> uniqueEntry(
+      cx->new_<ParserAtomEntry>(std::move(entry)));
+  if (!uniqueEntry) {
+    return RaiseParserAtomsOOMError(cx);
+  }
+  ParserAtomEntry* entryPtr = uniqueEntry.get();
+
+  if (!entrySet_.add(addPtr, std::move(uniqueEntry))) {
+    return RaiseParserAtomsOOMError(cx);
+  }
+  ParserAtomId id(entryPtr);
+
+  return id;
+}
+
+static const uint16_t MAX_LATIN1_CHAR = 0xff;
+
+template <typename CharT, typename InCharT>
+static void DrainChar16Seq(CharT* buf, InflatedChar16Sequence<InCharT> seq) {
+  static_assert(
+      std::is_same_v<CharT, char16_t> || std::is_same_v<CharT, Latin1Char>,
+      "Invalid target buffer type.");
+  CharT* cur = buf;
+  while (seq.hasMore()) {
+    char16_t ch = seq.next();
+    if constexpr (std::is_same_v<CharT, Latin1Char>) {
+      MOZ_ASSERT(ch <= MAX_LATIN1_CHAR);
+    }
+    *cur = ch;
+    cur++;
+  }
+}
+
+template <typename AtomCharT, typename SeqCharT>
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::internChar16Seq(
+    JSContext* cx, EntrySet::AddPtr add, InflatedChar16Sequence<SeqCharT> seq,
+    uint32_t length, HashNumber hash) {
+  using UniqueCharsT = mozilla::UniquePtr<AtomCharT[], JS::FreePolicy>;
+  UniqueCharsT copy(cx->pod_malloc<AtomCharT>(length));
+  if (!copy) {
+    return RaiseParserAtomsOOMError(cx);
+  }
+  DrainChar16Seq<AtomCharT, SeqCharT>(copy.get(), seq);
+  ParserAtomEntry ent = ParserAtomEntry::make(std::move(copy), length, hash);
+  return addEntry(cx, add, std::move(ent));
+}
+
+template <typename CharT>
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::lookupOrInternChar16Seq(
+    JSContext* cx, InflatedChar16Sequence<CharT> seq) {
+  // Check against well-known.
+  ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq);
+  if (wk) {
+    return wk;
+  }
+
+  // Check for existing atom.
+  SpecificParserAtomLookup<CharT> lookup(seq);
+  EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup);
+  if (add) {
+    return ParserAtomId(add->get());
+  }
+
+  // Compute the total length and the storage requirements.
+  bool wide = false;
+  uint32_t length = 0;
+  InflatedChar16Sequence<CharT> seqCopy = seq;
+  while (seqCopy.hasMore()) {
+    char16_t ch = seqCopy.next();
+    wide = wide || (ch > MAX_LATIN1_CHAR);
+    length += 1;
+  }
+
+  HashNumber hash = lookup.hash();
+  return wide ? internChar16Seq<char16_t>(cx, add, seq, length, hash)
+              : internChar16Seq<Latin1Char>(cx, add, seq, length, hash);
+}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::internChar16(
+    JSContext* cx, const char16_t* char16Ptr, uint32_t length) {
+  InflatedChar16Sequence<char16_t> seq(char16Ptr, length);
+
+  return lookupOrInternChar16Seq(cx, seq);
+}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::internAscii(
+    JSContext* cx, const char* asciiPtr, uint32_t length) {
+  const Latin1Char* latin1Ptr = reinterpret_cast<const Latin1Char*>(asciiPtr);
+  return internLatin1(cx, latin1Ptr, length);
+}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::internLatin1(
+    JSContext* cx, const Latin1Char* latin1Ptr, uint32_t length) {
+  // ASCII strings are strict subsets of Latin1 strings, an so can be used
+  // in the same (const) ways.
+  InflatedChar16Sequence<Latin1Char> seq(latin1Ptr, length);
+
+  // Check against well-known.
+  ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq);
+  if (wk) {
+    return wk;
+  }
+
+  // Look up.
+  SpecificParserAtomLookup<Latin1Char> lookup(seq);
+  EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup);
+  if (add) {
+    return ParserAtomId(add->get());
+  }
+
+  // Existing entry not found, heap-allocate a copy and add it to the table.
+  UniqueLatin1Chars copy = js::DuplicateString(cx, latin1Ptr, length);
+  if (!copy) {
+    return RaiseParserAtomsOOMError(cx);
+  }
+  ParserAtomEntry ent =
+      ParserAtomEntry::make(std::move(copy), length, lookup.hash());
+  return addEntry(cx, add, std::move(ent));
+}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::internUtf8(
+    JSContext* cx, const mozilla::Utf8Unit* utf8Ptr, uint32_t length) {
+  // If source text is ASCII, then the length of the target char buffer
+  // is the same as the length of the UTF8 input.  Convert it to a Latin1
+  // encoded string on the heap.
+  UTF8Chars utf8(utf8Ptr, length);
+  if (FindSmallestEncoding(utf8) == JS::SmallestEncoding::ASCII) {
+    // As ascii strings are a subset of Latin1 strings, and each encoding
+    // unit is the same size, we can reliably cast this `Utf8Unit*`
+    // to a `Latin1Char*`.
+    const Latin1Char* latin1Ptr = reinterpret_cast<const Latin1Char*>(utf8Ptr);
+    return internLatin1(cx, latin1Ptr, length);
+  }
+
+  InflatedChar16Sequence<mozilla::Utf8Unit> seq(utf8Ptr, length);
+
+  // Otherwise, slowpath lookup/interning path that identifies the
+  // proper target encoding.
+  return lookupOrInternChar16Seq(cx, seq);
+}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::internJSAtom(JSContext* cx,
+                                                              JSAtom* atom) {
+  JS::AutoCheckCannotGC nogc;
+
+  auto result =
+      atom->hasLatin1Chars()
+          ? internLatin1(cx, atom->latin1Chars(nogc), atom->length())
+          : internChar16(cx, atom->twoByteChars(nogc), atom->length());
+  if (result.isErr()) {
+    return result;
+  }
+  ParserAtomId id = result.unwrap();
+  id.entry()->setAtom(atom);
+  return id;
+}
+
+static void FillChar16Buffer(char16_t* buf, const ParserAtomEntry* ent) {
+  if (ent->hasLatin1Chars()) {
+    std::copy(ent->latin1Chars(), ent->latin1Chars() + ent->length(), buf);
+  } else {
+    std::copy(ent->twoByteChars(), ent->twoByteChars() + ent->length(), buf);
+  }
+}
+
+JS::Result<ParserAtomId, OOM&> ParserAtomsTable::concatAtoms(
+    JSContext* cx, ParserAtomId prefix, ParserAtomId suffix) {
+  const ParserAtomEntry* prefixEntry = prefix.entry();
+  const ParserAtomEntry* suffixEntry = suffix.entry();
+
+  bool latin1 = prefixEntry->hasLatin1Chars() && suffixEntry->hasLatin1Chars();
+  size_t prefixLength = prefixEntry->length();
+  size_t suffixLength = suffixEntry->length();
+  size_t concatLength = prefixLength + suffixLength;
+
+  if (latin1) {
+    // Concatenate a latin1 string and add it to the table.
+    UniqueLatin1Chars copy(cx->pod_malloc<Latin1Char>(concatLength));
+    if (!copy) {
+      return RaiseParserAtomsOOMError(cx);
+    }
+    mozilla::PodCopy(copy.get(), prefixEntry->latin1Chars(), prefixLength);
+    mozilla::PodCopy(copy.get() + prefixLength, suffixEntry->latin1Chars(),
+                     suffixLength);
+
+    InflatedChar16Sequence<Latin1Char> seq(copy.get(), concatLength);
+
+    // Check against well-known.
+    ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq);
+    if (wk) {
+      return wk;
+    }
+
+    SpecificParserAtomLookup<Latin1Char> lookup(seq);
+    EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup);
+    if (add) {
+      return ParserAtomId(add->get());
+    }
+
+    ParserAtomEntry ent =
+        ParserAtomEntry::make(std::move(copy), concatLength, lookup.hash());
+
+    return addEntry(cx, add, std::move(ent));
+  }
+
+  // Concatenate a char16 string and add it to the table.
+  UniqueTwoByteChars copy(cx->pod_malloc<char16_t>(concatLength));
+  if (!copy) {
+    return RaiseParserAtomsOOMError(cx);
+  }
+  FillChar16Buffer(copy.get(), prefixEntry);
+  FillChar16Buffer(copy.get() + prefixLength, suffixEntry);
+
+  InflatedChar16Sequence<char16_t> seq(copy.get(), concatLength);
+
+  // Check against well-known.
+  ParserAtomId wk = wellKnownTable_.lookupChar16Seq(seq);
+  if (wk) {
+    return wk;
+  }
+
+  SpecificParserAtomLookup<char16_t> lookup(seq);
+  EntrySet::AddPtr add = entrySet_.lookupForAdd(lookup);
+  if (add) {
+    return ParserAtomId(add->get());
+  }
+
+  ParserAtomEntry ent =
+      ParserAtomEntry::make(std::move(copy), concatLength, lookup.hash());
+
+  return addEntry(cx, add, std::move(ent));
+}
+
+template <typename CharT>
+ParserAtomId WellKnownParserAtoms::lookupChar16Seq(
+    InflatedChar16Sequence<CharT> seq) const {
+  SpecificParserAtomLookup<CharT> lookup(seq);
+  EntrySet::Ptr get = entrySet_.readonlyThreadsafeLookup(lookup);
+  if (get) {
+    return ParserAtomId(get->get());
+  }
+  return ParserAtomId::Invalid();
+}
+
+bool WellKnownParserAtoms::initSingle(JSContext* cx, ParserNameId* name,
+                                      const char* str) {
+  MOZ_ASSERT(name != nullptr);
+
+  unsigned int len = strlen(str);
+
+  MOZ_ASSERT(FindSmallestEncoding(UTF8Chars(str, len)) ==
+             JS::SmallestEncoding::ASCII);
+
+  UniqueLatin1Chars copy(cx->pod_malloc<Latin1Char>(len));
+  if (!copy) {
+    return false;
+  }
+  mozilla::PodCopy(copy.get(), reinterpret_cast<const Latin1Char*>(str), len);
+
+  InflatedChar16Sequence<Latin1Char> seq(copy.get(), len);
+  SpecificParserAtomLookup<Latin1Char> lookup(seq);
+
+  ParserAtomEntry ent =
+      ParserAtomEntry::make(std::move(copy), len, lookup.hash());
+
+  UniquePtr<ParserAtomEntry> uniqueEntry(
+      cx->new_<ParserAtomEntry>(std::move(ent)));
+  if (!uniqueEntry) {
+    return false;
+  }
+  ParserNameId nm(uniqueEntry.get());
+
+  if (!entrySet_.putNew(lookup, std::move(uniqueEntry))) {
+    return false;
+  }
+
+  *name = nm;
+  return true;
+}
+
+bool WellKnownParserAtoms::init(JSContext* cx) {
+#define COMMON_NAME_INIT(idpart, id, text) \
+  if (!initSingle(cx, &(id), text)) {      \
+    return false;                          \
+  }
+  FOR_EACH_COMMON_PROPERTYNAME(COMMON_NAME_INIT)
+#undef COMMON_NAME_INIT
+  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
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ParserAtom.h
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParserAtom_h
+#define frontend_ParserAtom_h
+
+#include "mozilla/HashFunctions.h"  // HashString
+#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 "vm/CommonPropertyNames.h"
+#include "vm/StringType.h"  // CompareChars, StringEqualsAscii
+
+namespace js {
+namespace frontend {
+
+class ParserNameId;
+
+template <typename CharT>
+class SpecificParserAtomLookup;
+
+class ParserAtomsTable;
+
+mozilla::GenericErrorResult<OOM&> RaiseParserAtomsOOMError(JSContext* cx);
+
+/**
+ * A ParserAtomEntry is an in-parser representation of an interned atomic
+ * string.  It mostly mirrors the information carried by a JSAtom*.
+ *
+ * ParserAtomEntry structs are individually heap-allocated and own their
+ * heap-allocated contents.
+ */
+class ParserAtomEntry {
+  friend class ParserAtomsTable;
+
+ public:
+  // Owned characters, either 8-bit Latin1, or 16-bit Char16
+  mozilla::Variant<JS::UniqueLatin1Chars, JS::UniqueTwoByteChars> chars_;
+
+  // The length of the buffer in chars_.
+  uint32_t length_;
+
+  // The JSAtom-compatible hash of the string.
+  HashNumber hash_;
+
+  // Used to dynamically optimize the mapping of ParserAtoms to JSAtom*s.
+  // If the entry comes from an atom or has been mapped to an
+  // atom previously, the atom reference is kept here.
+  mutable JSAtom* jsatom_ = nullptr;
+
+  template <typename CharsT>
+  ParserAtomEntry(CharsT&& chars, uint32_t length, HashNumber hash)
+      : chars_(std::forward<CharsT&&>(chars)), length_(length), hash_(hash) {}
+
+ public:
+  // ParserAtomEntries own their content buffers in chars_, and thus cannot
+  // be copy-constructed - as a new chars would need to be allocated.
+  ParserAtomEntry(const ParserAtomEntry&) = delete;
+
+  ParserAtomEntry(ParserAtomEntry&& other) = default;
+
+  template <typename CharT>
+  static ParserAtomEntry make(mozilla::UniquePtr<CharT[], JS::FreePolicy>&& ptr,
+                              uint32_t length, HashNumber hash) {
+    return ParserAtomEntry(std::move(ptr), length, hash);
+  }
+
+  bool hasLatin1Chars() const { return chars_.is<UniqueLatin1Chars>(); }
+  bool hasTwoByteChars() const { return chars_.is<UniqueTwoByteChars>(); }
+
+  const Latin1Char* latin1Chars() const {
+    MOZ_ASSERT(hasLatin1Chars());
+    return chars_.as<UniqueLatin1Chars>().get();
+  }
+  const char16_t* twoByteChars() const {
+    MOZ_ASSERT(hasTwoByteChars());
+    return chars_.as<UniqueTwoByteChars>().get();
+  }
+
+  bool isIndex(uint32_t* indexp) const;
+
+  HashNumber hash() const { return hash_; }
+  uint32_t length() const { return length_; }
+
+  bool equalsJSAtom(JSAtom* other) const;
+
+  template <typename CharT>
+  bool equalsSeq(HashNumber hash, InflatedChar16Sequence<CharT> seq) const;
+
+  void setAtom(JSAtom* atom) const {
+    MOZ_ASSERT(atom != nullptr);
+    if (jsatom_ != nullptr) {
+      MOZ_ASSERT(jsatom_ == atom);
+      return;
+    }
+    MOZ_ASSERT(equalsJSAtom(atom));
+    jsatom_ = atom;
+  }
+
+  // Convert this entry to a js-atom.  The first time this method is called
+  // the entry will cache the JSAtom pointer to return later.
+  JS::Result<JSAtom*, OOM&> toJSAtom(JSContext* cx) const;
+
+  // Convert this entry to a number.
+  bool toNumber(JSContext* cx, double* result) const;
+};
+
+class ParserAtomId {
+ protected:
+  const ParserAtomEntry* entry_;
+
+  struct InitInvalid {};
+
+  explicit ParserAtomId(InitInvalid) : entry_(nullptr) {}
+
+ public:
+  explicit ParserAtomId(const ParserAtomEntry* entry) : entry_(entry) {
+    MOZ_ASSERT(entry_ != nullptr);
+  }
+  ParserAtomId() = default;
+
+  bool isValid() const { return entry_ != nullptr; }
+  const ParserAtomEntry* entry() const {
+    MOZ_ASSERT(isValid());
+    return entry_;
+  }
+  MOZ_IMPLICIT operator bool() const { return isValid(); }
+
+  static ParserAtomId Invalid() { return ParserAtomId(InitInvalid{}); }
+
+  // As the "unchecked" tag signifies, this method should only be called
+  // after it has been confirmed that this atom is a name and not an index.
+  inline ParserNameId toNameIdUnchecked() const;
+
+  bool operator==(const ParserAtomId& other) { return entry_ == other.entry_; }
+  bool operator!=(const ParserAtomId& other) { return !(*this == other); }
+
+  bool isIndex(uint32_t* indexp) const { return entry()->isIndex(indexp); }
+  bool equalsJSAtom(JSAtom* other) const {
+    return entry()->equalsJSAtom(other);
+  }
+
+  size_t length() const { return entry()->length(); }
+  size_t empty() const { return length() == 0; }
+
+  struct Hasher {
+    using Lookup = ParserAtomId;
+
+    static inline HashNumber hash(const Lookup& l) {
+      return DefaultHasher<const ParserAtomEntry*>::hash(l.entry());
+    }
+    static inline bool match(const ParserAtomId& entry,
+                             const ParserAtomId& lookup) {
+      return lookup == entry;
+    }
+  };
+};
+
+class ParserNameId : public ParserAtomId {
+  explicit ParserNameId(InitInvalid) : ParserAtomId(InitInvalid{}) {}
+
+ public:
+  ParserNameId() = default;
+  explicit ParserNameId(const ParserAtomEntry* entry) : ParserAtomId(entry) {}
+  ParserNameId(const ParserNameId& other) = default;
+
+  static ParserNameId Invalid() { return ParserNameId(InitInvalid{}); }
+};
+
+inline ParserNameId ParserAtomId::toNameIdUnchecked() const {
+  return ParserNameId(entry_);
+}
+
+UniqueChars ParserAtomToPrintableString(JSContext* cx, ParserAtomId atom);
+
+/**
+ * A lookup structure that allows for querying ParserAtoms in
+ * a hashtable using a flexible input type that supports string
+ * representations of various forms.
+ */
+class ParserAtomLookup {
+ protected:
+  HashNumber hash_;
+
+  ParserAtomLookup(HashNumber hash) : hash_(hash) {}
+
+ public:
+  HashNumber hash() const { return hash_; }
+
+  virtual bool equalsEntry(const ParserAtomEntry* entry) const = 0;
+};
+
+struct ParserAtomLookupHasher {
+  using Lookup = ParserAtomLookup;
+
+  static inline HashNumber hash(const Lookup& l) { return l.hash(); }
+  static inline bool match(const UniquePtr<ParserAtomEntry>& entry,
+                           const Lookup& l) {
+    return l.equalsEntry(entry.get());
+  }
+};
+
+/**
+ * WellKnown maintains a well-structured reference to common names.
+ * A single instance of it is held on the main Runtime, and allows
+ * for the looking up of names, but not addition after initialization.
+ */
+class WellKnownParserAtoms {
+ public:
+  /* Various built-in or commonly-used names. */
+#define PROPERTYNAME_FIELD(idpart, id, text) ParserNameId id{};
+  FOR_EACH_COMMON_PROPERTYNAME(PROPERTYNAME_FIELD)
+#undef PROPERTYNAME_FIELD
+
+ private:
+  using EntrySet = HashSet<UniquePtr<ParserAtomEntry>, ParserAtomLookupHasher,
+                           TempAllocPolicy>;
+  EntrySet entrySet_;
+
+  bool initSingle(JSContext* cx, ParserNameId* name, const char* str);
+
+ public:
+  explicit WellKnownParserAtoms(JSContext* cx) : entrySet_(cx) {}
+
+  bool init(JSContext* cx);
+
+  template <typename CharT>
+  ParserAtomId lookupChar16Seq(InflatedChar16Sequence<CharT> seq) const;
+};
+
+/**
+ * A ParserAtomsTable owns and manages the vector of ParserAtom entries
+ * associated with a given compile session.
+ */
+class ParserAtomsTable {
+ private:
+  using EntrySet = HashSet<UniquePtr<ParserAtomEntry>, ParserAtomLookupHasher,
+                           TempAllocPolicy>;
+  EntrySet entrySet_;
+  const WellKnownParserAtoms& wellKnownTable_;
+
+ public:
+  explicit ParserAtomsTable(JSContext* cx);
+
+ private:
+  JS::Result<ParserAtomId, OOM&> addEntry(JSContext* cx,
+                                          EntrySet::AddPtr addPtr,
+                                          ParserAtomEntry&& entry);
+
+  template <typename AtomCharT, typename SeqCharT>
+  JS::Result<ParserAtomId, OOM&> internChar16Seq(
+      JSContext* cx, EntrySet::AddPtr add, InflatedChar16Sequence<SeqCharT> seq,
+      uint32_t length, HashNumber hash);
+
+  template <typename CharT>
+  JS::Result<ParserAtomId, OOM&> lookupOrInternChar16Seq(
+      JSContext* cx, InflatedChar16Sequence<CharT> seq);
+
+ public:
+  JS::Result<ParserAtomId, OOM&> internChar16(JSContext* cx,
+                                              const char16_t* char16Ptr,
+                                              uint32_t length);
+
+  JS::Result<ParserAtomId, OOM&> internAscii(JSContext* cx,
+                                             const char* asciiPtr,
+                                             uint32_t length);
+
+  JS::Result<ParserAtomId, OOM&> internLatin1(JSContext* cx,
+                                              const Latin1Char* latin1Ptr,
+                                              uint32_t length);
+
+  JS::Result<ParserAtomId, OOM&> internUtf8(JSContext* cx,
+                                            const mozilla::Utf8Unit* utf8Ptr,
+                                            uint32_t length);
+
+  JS::Result<ParserAtomId, OOM&> internJSAtom(JSContext* cx, JSAtom* atom);
+
+  JS::Result<ParserAtomId, OOM&> concatAtoms(JSContext* cx, ParserAtomId prefix,
+                                             ParserAtomId suffix);
+
+  // Lift this code
+  // Once all the parser code has been changed to use a ParserAtomId, these
+  // can go away.
+  JS::Result<JSAtom*, OOM&> toJSAtom(JSContext* cx, ParserAtomId id) const {
+    return id.entry()->toJSAtom(cx);
+  }
+};
+
+template <typename CharT>
+class SpecificParserAtomLookup : public ParserAtomLookup {
+  // The sequence of characters to look up.
+  InflatedChar16Sequence<CharT> seq_;
+
+ public:
+  explicit SpecificParserAtomLookup(const InflatedChar16Sequence<CharT>& seq)
+      : SpecificParserAtomLookup(seq, computeHash(seq)) {}
+
+  SpecificParserAtomLookup(const InflatedChar16Sequence<CharT>& seq,
+                           HashNumber hash)
+      : ParserAtomLookup(hash), seq_(seq) {
+    MOZ_ASSERT(computeHash(seq_) == hash);
+  }
+
+  virtual bool equalsEntry(const ParserAtomEntry* entry) const override {
+    return entry->equalsSeq<CharT>(hash_, seq_);
+  }
+
+ private:
+  static HashNumber computeHash(InflatedChar16Sequence<CharT> seq) {
+    HashNumber hash = 0;
+    while (seq.hasMore()) {
+      hash = mozilla::AddToHash(hash, seq.next());
+    }
+    return hash;
+  }
+};
+
+template <typename CharT>
+inline bool ParserAtomEntry::equalsSeq(
+    HashNumber hash, InflatedChar16Sequence<CharT> seq) const {
+  // Compare hashes first.
+  if (hash_ != hash) {
+    return false;
+  }
+
+  if (hasTwoByteChars()) {
+    const char16_t* chars = twoByteChars();
+    for (uint32_t i = 0; i < length_; i++) {
+      if (!seq.hasMore() || chars[i] != seq.next()) {
+        return false;
+      }
+    }
+    if (seq.hasMore()) {
+      return false;
+    }
+
+  } else {
+    const Latin1Char* chars = latin1Chars();
+    for (uint32_t i = 0; i < length_; i++) {
+      if (!seq.hasMore() || char16_t(chars[i]) != seq.next()) {
+        return false;
+      }
+    }
+    if (seq.hasMore()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif  // frontend_ParserAtom_h
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -2226,33 +2226,33 @@ MOZ_MUST_USE bool TokenStreamSpecific<Un
   JSAtom* atom;
   if (MOZ_UNLIKELY(escaping == IdentifierEscapes::SawUnicodeEscape)) {
     // Identifiers containing Unicode escapes have to be converted into
     // tokenbuf before atomizing.
     if (!putIdentInCharBuffer(identStart)) {
       return false;
     }
 
-    atom = drainCharBufferIntoAtom(anyCharsAccess().cx);
+    atom = drainCharBufferIntoAtom();
   } else {
     // Escape-free identifiers can be created directly from sourceUnits.
     const Unit* chars = identStart;
     size_t length = this->sourceUnits.addressOfNextCodeUnit() - identStart;
 
     // Private identifiers start with a '#', and so cannot be reserved words.
     if (visibility == NameVisibility::Public) {
       // Represent reserved words lacking escapes as reserved word tokens.
       if (const ReservedWordInfo* rw = FindReservedWord(chars, length)) {
         noteBadToken.release();
         newSimpleToken(rw->tokentype, start, modifier, out);
         return true;
       }
     }
 
-    atom = atomizeSourceChars(anyCharsAccess().cx, MakeSpan(chars, length));
+    atom = atomizeSourceChars(MakeSpan(chars, length));
   }
   if (!atom) {
     return false;
   }
 
   noteBadToken.release();
   if (visibility == NameVisibility::Private) {
     newPrivateNameToken(atom->asPropertyName(), start, modifier, out);
@@ -3663,17 +3663,17 @@ bool TokenStreamSpecific<Unit, AnyCharsA
       break;
     }
 
     if (!this->charBuffer.append(unit)) {
       return false;
     }
   }
 
-  JSAtom* atom = drainCharBufferIntoAtom(anyCharsAccess().cx);
+  JSAtom* atom = drainCharBufferIntoAtom();
   if (!atom) {
     return false;
   }
 
   noteBadToken.release();
 
   MOZ_ASSERT_IF(!parsingTemplate, !templateHead);
 
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -1532,18 +1532,32 @@ class TokenStreamCharsShared {
    * (The code point's exact value might not be used, however, if subsequent
    * code observes that |unit| is part of a LineTerminatorSequence.)
    */
   static constexpr MOZ_ALWAYS_INLINE MOZ_MUST_USE bool isAsciiCodePoint(
       int32_t unit) {
     return mozilla::IsAscii(static_cast<char32_t>(unit));
   }
 
-  JSAtom* drainCharBufferIntoAtom(JSContext* cx) {
-    JSAtom* atom = AtomizeChars(cx, charBuffer.begin(), charBuffer.length());
+  JSAtom* drainCharBufferIntoAtom() {
+    JSAtom* atom = AtomizeChars(this->compilationInfo->cx, charBuffer.begin(),
+                                charBuffer.length());
+    if (!atom) {
+      return nullptr;
+    }
+
+    // Add to parser atoms table.
+#ifdef JS_PARSER_ATOMS
+    auto maybeId = this->compilationInfo->parserAtoms.internChar16(
+        this->compilationInfo->cx, charBuffer.begin(), charBuffer.length());
+    if (maybeId.isErr()) {
+      return nullptr;
+    }
+#endif  // JS_PARSER_ATOMS
+
     charBuffer.clear();
     return atom;
   }
 
  protected:
   void adoptState(TokenStreamCharsShared& other) {
     // The other stream's buffer may contain information for a
     // gotten-then-ungotten token, that we must transfer into this stream so
@@ -1597,18 +1611,17 @@ class TokenStreamCharsBase : public Toke
   void ungetCodeUnit(int32_t c) {
     if (c == EOF) {
       return;
     }
 
     sourceUnits.ungetCodeUnit();
   }
 
-  static MOZ_ALWAYS_INLINE JSAtom* atomizeSourceChars(
-      JSContext* cx, mozilla::Span<const Unit> units);
+  MOZ_ALWAYS_INLINE JSAtom* atomizeSourceChars(mozilla::Span<const Unit> units);
 
   /**
    * Try to match a non-LineTerminator ASCII code point.  Return true iff it
    * was matched.
    */
   bool matchCodeUnit(char expect) {
     MOZ_ASSERT(mozilla::IsAscii(expect));
     MOZ_ASSERT(expect != '\r');
@@ -1684,28 +1697,55 @@ inline mozilla::Utf8Unit TokenStreamChar
 }
 
 template <typename Unit>
 inline void TokenStreamCharsBase<Unit>::consumeKnownCodeUnit(int32_t unit) {
   sourceUnits.consumeKnownCodeUnit(toUnit(unit));
 }
 
 template <>
-/* static */ MOZ_ALWAYS_INLINE JSAtom*
-TokenStreamCharsBase<char16_t>::atomizeSourceChars(
-    JSContext* cx, mozilla::Span<const char16_t> units) {
-  return AtomizeChars(cx, units.data(), units.size());
+MOZ_ALWAYS_INLINE JSAtom* TokenStreamCharsBase<char16_t>::atomizeSourceChars(
+    mozilla::Span<const char16_t> units) {
+  JSAtom* atom =
+      AtomizeChars(this->compilationInfo->cx, units.data(), units.size());
+  if (!atom) {
+    return nullptr;
+  }
+
+#ifdef JS_PARSER_ATOMS
+  auto maybeId = this->compilationInfo->parserAtoms.internChar16(
+      this->compilationInfo->cx, units.data(), units.size());
+  if (maybeId.isErr()) {
+    return nullptr;
+  }
+#endif  // JS_PARSER_ATOMS
+
+  return atom;
 }
 
 template <>
 /* static */ MOZ_ALWAYS_INLINE JSAtom*
 TokenStreamCharsBase<mozilla::Utf8Unit>::atomizeSourceChars(
-    JSContext* cx, mozilla::Span<const mozilla::Utf8Unit> units) {
+    mozilla::Span<const mozilla::Utf8Unit> units) {
   auto chars = ToCharSpan(units);
-  return AtomizeUTF8Chars(cx, chars.data(), chars.size());
+  JSAtom* atom =
+      AtomizeUTF8Chars(this->compilationInfo->cx, chars.data(), chars.size());
+  if (!atom) {
+    return nullptr;
+  }
+
+#ifdef JS_PARSER_ATOMS
+  auto maybeId = this->compilationInfo->parserAtoms.internUtf8(
+      this->compilationInfo->cx, units.data(), units.size());
+  if (maybeId.isErr()) {
+    return nullptr;
+  }
+#endif  // JS_PARSER_ATOMS
+
+  return atom;
 }
 
 template <typename Unit>
 class SpecializedTokenStreamCharsBase;
 
 template <>
 class SpecializedTokenStreamCharsBase<char16_t>
     : public TokenStreamCharsBase<char16_t> {
@@ -2136,17 +2176,17 @@ class GeneralTokenStreamChars : public S
 
     // Template literals normalize only '\r' and "\r\n" to '\n'; Unicode
     // separators don't need special handling.
     // https://tc39.github.io/ecma262/#sec-static-semantics-tv-and-trv
     if (!fillCharBufferFromSourceNormalizingAsciiLineBreaks(cur, end)) {
       return nullptr;
     }
 
-    return drainCharBufferIntoAtom(anyChars.cx);
+    return drainCharBufferIntoAtom();
   }
 };
 
 template <typename Unit, class AnyCharsAccess>
 class TokenStreamChars;
 
 template <class AnyCharsAccess>
 class TokenStreamChars<char16_t, AnyCharsAccess>
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -53,16 +53,17 @@ UNIFIED_SOURCES += [
     'NameFunctions.cpp',
     'NameOpEmitter.cpp',
     'ObjectEmitter.cpp',
     'ObjLiteral.cpp',
     'OptionalEmitter.cpp',
     'ParseContext.cpp',
     'ParseNode.cpp',
     'ParseNodeVerify.cpp',
+    'ParserAtom.cpp',
     'PropOpEmitter.cpp',
     'SharedContext.cpp',
     'SourceNotes.cpp',
     'Stencil.cpp',
     'SwitchEmitter.cpp',
     'TDZCheckCache.cpp',
     'TokenStream.cpp',
     'TryEmitter.cpp',
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -459,16 +459,20 @@ JS_PUBLIC_API bool JS::InitSelfHostedCod
   AutoNoteSingleThreadedRegion anstr;
 
   JSRuntime* rt = cx->runtime();
 
   if (!rt->initializeAtoms(cx)) {
     return false;
   }
 
+  if (!rt->initializeParserAtoms(cx)) {
+    return false;
+  }
+
 #ifndef JS_CODEGEN_NONE
   if (!rt->createJitRuntime(cx)) {
     return false;
   }
 #endif
 
   if (!rt->initSelfHosting(cx)) {
     return false;
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1656,18 +1656,18 @@ bool JS_FASTCALL js::NumberValueToString
     cstrlen = strlen(cstr);
   }
 
   MOZ_ASSERT(!cbuf.dbuf && cstrlen < cbuf.sbufSize);
   return sb.append(cstr, cstrlen);
 }
 
 template <typename CharT>
-static bool CharsToNumber(JSContext* cx, const CharT* chars, size_t length,
-                          double* result) {
+static bool CharsToNumberImpl(JSContext* cx, const CharT* chars, size_t length,
+                              double* result) {
   if (length == 1) {
     CharT c = chars[0];
     if ('0' <= c && c <= '9') {
       *result = c - '0';
     } else if (unicode::IsSpace(c)) {
       *result = 0.0;
     } else {
       *result = GenericNaN();
@@ -1726,16 +1726,26 @@ static bool CharsToNumber(JSContext* cx,
     *result = GenericNaN();
   } else {
     *result = d;
   }
 
   return true;
 }
 
+bool js::CharsToNumber(JSContext* cx, const Latin1Char* chars, size_t length,
+                       double* result) {
+  return CharsToNumberImpl(cx, chars, length, result);
+}
+
+bool js::CharsToNumber(JSContext* cx, const char16_t* chars, size_t length,
+                       double* result) {
+  return CharsToNumberImpl(cx, chars, length, result);
+}
+
 bool js::StringToNumber(JSContext* cx, JSString* str, double* result) {
   AutoCheckCannotGC nogc;
   JSLinearString* linearStr = str->ensureLinear(cx);
   if (!linearStr) {
     return false;
   }
 
   if (str->hasIndexValue()) {
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -171,16 +171,21 @@ extern MOZ_MUST_USE bool GetDecimalInteg
  * This is like GetDecimalInteger, but also allows non-integer numbers. It
  * should only be used when the characters are known to match |DecimalLiteral|,
  * cf. ES2020, 11.8.3 Numeric Literals.
  */
 template <typename CharT>
 extern MOZ_MUST_USE bool GetDecimalNonInteger(JSContext* cx, const CharT* start,
                                               const CharT* end, double* dp);
 
+bool CharsToNumber(JSContext* cx, const Latin1Char* chars, size_t length,
+                   double* result);
+bool CharsToNumber(JSContext* cx, const char16_t* chars, size_t length,
+                   double* result);
+
 extern MOZ_MUST_USE bool StringToNumber(JSContext* cx, JSString* str,
                                         double* result);
 
 extern MOZ_MUST_USE bool StringToNumberPure(JSContext* cx, JSString* str,
                                             double* result);
 
 /* ES5 9.3 ToNumber, overwriting *vp with the appropriate number value. */
 MOZ_ALWAYS_INLINE MOZ_MUST_USE bool ToNumber(JSContext* cx,
--- a/js/src/util/Text.cpp
+++ b/js/src/util/Text.cpp
@@ -68,16 +68,29 @@ UniqueChars js::DuplicateStringToArena(a
   if (!ret) {
     return nullptr;
   }
   PodCopy(ret.get(), s, n);
   ret[n] = '\0';
   return ret;
 }
 
+UniqueLatin1Chars js::DuplicateStringToArena(arena_id_t destArenaId,
+                                             JSContext* cx,
+                                             const JS::Latin1Char* s,
+                                             size_t n) {
+  auto ret = cx->make_pod_arena_array<Latin1Char>(destArenaId, n + 1);
+  if (!ret) {
+    return nullptr;
+  }
+  PodCopy(ret.get(), s, n);
+  ret[n] = '\0';
+  return ret;
+}
+
 UniqueTwoByteChars js::DuplicateStringToArena(arena_id_t destArenaId,
                                               JSContext* cx,
                                               const char16_t* s) {
   return DuplicateStringToArena(destArenaId, cx, s, js_strlen(s));
 }
 
 UniqueTwoByteChars js::DuplicateStringToArena(arena_id_t destArenaId,
                                               JSContext* cx, const char16_t* s,
@@ -101,16 +114,29 @@ UniqueChars js::DuplicateStringToArena(a
   if (!ret) {
     return nullptr;
   }
   PodCopy(ret.get(), s, n);
   ret[n] = '\0';
   return ret;
 }
 
+UniqueLatin1Chars js::DuplicateStringToArena(arena_id_t destArenaId,
+                                             const JS::Latin1Char* s,
+                                             size_t n) {
+  UniqueLatin1Chars ret(
+      js_pod_arena_malloc<JS::Latin1Char>(destArenaId, n + 1));
+  if (!ret) {
+    return nullptr;
+  }
+  PodCopy(ret.get(), s, n);
+  ret[n] = '\0';
+  return ret;
+}
+
 UniqueTwoByteChars js::DuplicateStringToArena(arena_id_t destArenaId,
                                               const char16_t* s) {
   return DuplicateStringToArena(destArenaId, s, js_strlen(s));
 }
 
 UniqueTwoByteChars js::DuplicateStringToArena(arena_id_t destArenaId,
                                               const char16_t* s, size_t n) {
   UniqueTwoByteChars ret(js_pod_arena_malloc<char16_t>(destArenaId, n + 1));
@@ -125,16 +151,21 @@ UniqueTwoByteChars js::DuplicateStringTo
 UniqueChars js::DuplicateString(JSContext* cx, const char* s, size_t n) {
   return DuplicateStringToArena(js::MallocArena, cx, s, n);
 }
 
 UniqueChars js::DuplicateString(JSContext* cx, const char* s) {
   return DuplicateStringToArena(js::MallocArena, cx, s);
 }
 
+UniqueLatin1Chars js::DuplicateString(JSContext* cx, const JS::Latin1Char* s,
+                                      size_t n) {
+  return DuplicateStringToArena(js::MallocArena, cx, s, n);
+}
+
 UniqueTwoByteChars js::DuplicateString(JSContext* cx, const char16_t* s) {
   return DuplicateStringToArena(js::MallocArena, cx, s);
 }
 
 UniqueTwoByteChars js::DuplicateString(JSContext* cx, const char16_t* s,
                                        size_t n) {
   return DuplicateStringToArena(js::MallocArena, cx, s, n);
 }
@@ -142,16 +173,20 @@ UniqueTwoByteChars js::DuplicateString(J
 UniqueChars js::DuplicateString(const char* s) {
   return DuplicateStringToArena(js::MallocArena, s);
 }
 
 UniqueChars js::DuplicateString(const char* s, size_t n) {
   return DuplicateStringToArena(js::MallocArena, s, n);
 }
 
+UniqueLatin1Chars js::DuplicateString(const JS::Latin1Char* s, size_t n) {
+  return DuplicateStringToArena(js::MallocArena, s, n);
+}
+
 UniqueTwoByteChars js::DuplicateString(const char16_t* s) {
   return DuplicateStringToArena(js::MallocArena, s);
 }
 
 UniqueTwoByteChars js::DuplicateString(const char16_t* s, size_t n) {
   return DuplicateStringToArena(js::MallocArena, s, n);
 }
 
--- a/js/src/util/Text.h
+++ b/js/src/util/Text.h
@@ -103,16 +103,20 @@ static inline const CharT* SkipSpace(con
 }
 
 extern UniqueChars DuplicateStringToArena(arena_id_t destArenaId, JSContext* cx,
                                           const char* s);
 
 extern UniqueChars DuplicateStringToArena(arena_id_t destArenaId, JSContext* cx,
                                           const char* s, size_t n);
 
+extern UniqueLatin1Chars DuplicateStringToArena(arena_id_t destArenaId,
+                                                JSContext* cx,
+                                                const Latin1Char* s, size_t n);
+
 extern UniqueTwoByteChars DuplicateStringToArena(arena_id_t destArenaId,
                                                  JSContext* cx,
                                                  const char16_t* s);
 
 extern UniqueTwoByteChars DuplicateStringToArena(arena_id_t destArenaId,
                                                  JSContext* cx,
                                                  const char16_t* s, size_t n);
 
@@ -121,39 +125,48 @@ extern UniqueTwoByteChars DuplicateStrin
  * yourself.
  */
 extern UniqueChars DuplicateStringToArena(arena_id_t destArenaId,
                                           const char* s);
 
 extern UniqueChars DuplicateStringToArena(arena_id_t destArenaId, const char* s,
                                           size_t n);
 
+extern UniqueLatin1Chars DuplicateStringToArena(arena_id_t destArenaId,
+                                                const JS::Latin1Char* s,
+                                                size_t n);
+
 extern UniqueTwoByteChars DuplicateStringToArena(arena_id_t destArenaId,
                                                  const char16_t* s);
 
 extern UniqueTwoByteChars DuplicateStringToArena(arena_id_t destArenaId,
                                                  const char16_t* s, size_t n);
 
 extern UniqueChars DuplicateString(JSContext* cx, const char* s);
 
 extern UniqueChars DuplicateString(JSContext* cx, const char* s, size_t n);
 
+extern UniqueLatin1Chars DuplicateString(JSContext* cx, const JS::Latin1Char* s,
+                                         size_t n);
+
 extern UniqueTwoByteChars DuplicateString(JSContext* cx, const char16_t* s);
 
 extern UniqueTwoByteChars DuplicateString(JSContext* cx, const char16_t* s,
                                           size_t n);
 
 /*
  * These variants do not report OOMs, you must arrange for OOMs to be reported
  * yourself.
  */
 extern UniqueChars DuplicateString(const char* s);
 
 extern UniqueChars DuplicateString(const char* s, size_t n);
 
+extern UniqueLatin1Chars DuplicateString(const JS::Latin1Char* s, size_t n);
+
 extern UniqueTwoByteChars DuplicateString(const char16_t* s);
 
 extern UniqueTwoByteChars DuplicateString(const char16_t* s, size_t n);
 
 /*
  * Inflate bytes in ASCII encoding to char16_t code units. Return null on error,
  * otherwise return the char16_t buffer that was malloc'ed. A null char is
  * appended.
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -35,16 +35,20 @@ struct JS_PUBLIC_API JSContext;
 struct DtoaState;
 
 namespace js {
 
 class AutoAllocInAtomsZone;
 class AutoMaybeLeaveAtomsZone;
 class AutoRealm;
 
+namespace frontend {
+class WellKnownParserAtoms;
+}  // namespace frontend
+
 namespace jit {
 class JitActivation;
 class JitContext;
 class DebugModeOSRVolatileJitFrameIter;
 }  // namespace jit
 
 namespace gc {
 class AutoCheckCanAccessAtomsDuringGC;
@@ -260,16 +264,19 @@ struct JS_PUBLIC_API JSContext : public 
   uint32_t getAndResetAllocsThisZoneSinceMinorGC() {
     uint32_t allocs = allocsThisZoneSinceMinorGC_;
     allocsThisZoneSinceMinorGC_ = 0;
     return allocs;
   }
 
   // Accessors for immutable runtime data.
   JSAtomState& names() { return *runtime_->commonNames; }
+  js::frontend::WellKnownParserAtoms& parserNames() {
+    return *runtime_->commonParserNames;
+  }
   js::StaticStrings& staticStrings() { return *runtime_->staticStrings; }
   js::SharedImmutableStringsCache& sharedImmutableStrings() {
     return runtime_->sharedImmutableStrings();
   }
   bool permanentAtomsPopulated() { return runtime_->permanentAtomsPopulated(); }
   const js::FrozenAtomSet& permanentAtoms() {
     return *runtime_->permanentAtoms();
   }
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -105,16 +105,20 @@ class CompileRuntime;
 
 #ifdef JS_SIMULATOR_ARM64
 typedef vixl::Simulator Simulator;
 #elif defined(JS_SIMULATOR)
 class Simulator;
 #endif
 }  // namespace jit
 
+namespace frontend {
+class WellKnownParserAtoms;
+}  // namespace frontend
+
 // [SMDOC] JS Engine Threading
 //
 // Threads interacting with a runtime are divided into two categories:
 //
 // - The main thread is capable of running JS. There's at most one main thread
 //   per runtime.
 //
 // - Helper threads do not run JS, and are controlled or triggered by activity
@@ -740,17 +744,19 @@ struct JSRuntime {
   // can only be done from the main thread.
   js::MainThreadOrGCTaskData<js::SymbolRegistry> symbolRegistry_;
 
   js::WriteOnceData<js::AtomSet*> permanentAtomsDuringInit_;
   js::WriteOnceData<js::FrozenAtomSet*> permanentAtoms_;
 
  public:
   bool initializeAtoms(JSContext* cx);
+  bool initializeParserAtoms(JSContext* cx);
   void finishAtoms();
+  void finishParserAtoms();
   bool atomsAreFinished() const {
     return !atoms_ && !permanentAtomsDuringInit_;
   }
 
   js::AtomsTable* atomsForSweeping() {
     MOZ_ASSERT(JS::RuntimeHeapIsCollecting());
     return atoms_;
   }
@@ -781,16 +787,17 @@ struct JSRuntime {
   // shared with another, longer living runtime through |parentRuntime| and
   // can be freely accessed with no locking necessary.
 
   // Permanent atoms pre-allocated for general use.
   js::WriteOnceData<js::StaticStrings*> staticStrings;
 
   // Cached pointers to various permanent property names.
   js::WriteOnceData<JSAtomState*> commonNames;
+  js::WriteOnceData<js::frontend::WellKnownParserAtoms*> commonParserNames;
 
   // All permanent atoms in the runtime, other than those in staticStrings.
   // Access to this does not require a lock because it is frozen and thus
   // read-only.
   const js::FrozenAtomSet* permanentAtoms() const {
     MOZ_ASSERT(permanentAtomsPopulated());
     return permanentAtoms_.ref();
   }
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -1147,19 +1147,17 @@ bool js::StringEqualsAscii(JSLinearStrin
 
   AutoCheckCannotGC nogc;
   return str->hasLatin1Chars()
              ? ArrayEqual(latin1, str->latin1Chars(nogc), length)
              : EqualChars(latin1, str->twoByteChars(nogc), length);
 }
 
 template <typename CharT>
-/* static */
-bool JSLinearString::isIndexSlow(const CharT* s, size_t length,
-                                 uint32_t* indexp) {
+bool js::CheckStringIsIndex(const CharT* s, size_t length, uint32_t* indexp) {
   MOZ_ASSERT(length > 0);
   MOZ_ASSERT(length <= UINT32_CHAR_BUFFER_LENGTH);
   MOZ_ASSERT(IsAsciiDigit(*s),
              "caller's fast path must have checked first char");
 
   RangedPtr<const CharT> cp(s, length);
   const RangedPtr<const CharT> end(s + length, s, length);
 
@@ -1189,16 +1187,28 @@ bool JSLinearString::isIndexSlow(const C
       (oldIndex == UINT32_MAX / 10 && c <= (UINT32_MAX % 10))) {
     *indexp = index;
     return true;
   }
 
   return false;
 }
 
+template bool js::CheckStringIsIndex(const Latin1Char* s, size_t length,
+                                     uint32_t* indexp);
+template bool js::CheckStringIsIndex(const char16_t* s, size_t length,
+                                     uint32_t* indexp);
+
+template <typename CharT>
+/* static */
+bool JSLinearString::isIndexSlow(const CharT* s, size_t length,
+                                 uint32_t* indexp) {
+  return js::CheckStringIsIndex(s, length, indexp);
+}
+
 template bool JSLinearString::isIndexSlow(const Latin1Char* s, size_t length,
                                           uint32_t* indexp);
 
 template bool JSLinearString::isIndexSlow(const char16_t* s, size_t length,
                                           uint32_t* indexp);
 
 /*
  * Declare length-2 strings. We only store strings where both characters are
--- a/js/src/vm/StringType.h
+++ b/js/src/vm/StringType.h
@@ -1254,16 +1254,19 @@ MOZ_ALWAYS_INLINE JSAtom* JSLinearString
   setFlagBit(ATOM_BIT);
   JSAtom* atom = &asAtom();
   atom->initHash(hash);
   return atom;
 }
 
 namespace js {
 
+template <typename CharT>
+bool CheckStringIsIndex(const CharT* s, size_t length, uint32_t* indexp);
+
 /**
  * An indexable characters class exposing unaligned, little-endian encoded
  * char16_t data.
  */
 class LittleEndianChars {
  public:
   explicit constexpr LittleEndianChars(const uint8_t* leTwoByte)
       : current(leTwoByte) {}