Merge mozilla-central to autoland. a=merge CLOSED TREE
authorOana Pop Rus <opoprus@mozilla.com>
Tue, 12 Mar 2019 06:53:06 +0200
changeset 521484 d8b3da69c3a055f6cdc996e7dcd03096c2eb7682
parent 521483 d25ff3b04eeb7529ad27086f151a1d3b7231e5cc (current diff)
parent 521370 b9d87882a36584e2f17331b652cbc8681aee17ce (diff)
child 521485 89988d424c06a3333eb15a880bc1c4c6bbb1c070
push id10866
push usernerli@mozilla.com
push dateTue, 12 Mar 2019 18:59:09 +0000
treeherdermozilla-beta@445c24a51727 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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
Merge mozilla-central to autoland. a=merge CLOSED TREE
js/src/jit/CodeGenerator.cpp
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -51,20 +51,22 @@ class ElementStyle {
     if (!("userProperties" in this.store)) {
       this.store.userProperties = new UserProperties();
     }
 
     if (!("disabled" in this.store)) {
       this.store.disabled = new WeakMap();
     }
 
-    this.onStyleSheetUpdated = this.onStyleSheetUpdated.bind(this);
+    this.onRefresh = this.onRefresh.bind(this);
 
     if (this.ruleView.isNewRulesView) {
-      this.pageStyle.on("stylesheet-updated", this.onStyleSheetUpdated);
+      this.pageStyle.on("stylesheet-updated", this.onRefresh);
+      this.ruleView.inspector.styleChangeTracker.on("style-changed", this.onRefresh);
+      this.ruleView.selection.on("pseudoclass", this.onRefresh);
     }
   }
 
   destroy() {
     if (this.destroyed) {
       return;
     }
 
@@ -74,17 +76,19 @@ class ElementStyle {
       if (rule.editor) {
         rule.editor.destroy();
       }
 
       rule.destroy();
     }
 
     if (this.ruleView.isNewRulesView) {
-      this.pageStyle.off("stylesheet-updated", this.onStyleSheetUpdated);
+      this.pageStyle.off("stylesheet-updated", this.onRefresh);
+      this.ruleView.inspector.styleChangeTracker.off("style-changed", this.onRefresh);
+      this.ruleView.selection.off("pseudoclass", this.onRefresh);
     }
   }
 
   /**
    * Called by the Rule object when it has been changed through the
    * setProperty* methods.
    */
   _changed() {
@@ -129,16 +133,18 @@ class ElementStyle {
         this.subscribeRulesToLocationChange();
       }
 
       // We're done with the previous list of rules.
       for (const r of existingRules) {
         if (r && r.editor) {
           r.editor.destroy();
         }
+
+        r.destroy();
       }
 
       return undefined;
     }).catch(e => {
       // populate is often called after a setTimeout,
       // the connection may already be closed.
       if (this.destroyed) {
         return promise.resolve(undefined);
@@ -663,20 +669,20 @@ class ElementStyle {
   * @return {String} the variable's value or null if the variable is
   *         not defined.
   */
   getVariable(name) {
     return this.variables.get(name);
   }
 
   /**
-   * Handler for page style events "stylesheet-updated". Refreshes the list of rules on
-   * the page.
+   * Handler for "stylesheet-updated", "style-changed" and "pseudoclass" events.
+   * Refreshes the list of rules on the page.
    */
-  async onStyleSheetUpdated() {
+  async onRefresh() {
     // Repopulate the element style once the current modifications are done.
     const promises = [];
     for (const rule of this.rules) {
       if (rule._applyingModifications) {
         promises.push(rule._applyingModifications);
       }
     }
 
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -243,18 +243,13 @@ class TextProperty {
     // When adding a new property in the rule-view, the TextProperty object is
     // created right away before the rule gets updated on the server, so we're
     // not going to find the corresponding declaration object yet. Default to
     // true.
     if (!this.rule.domRule.declarations[selfIndex]) {
       return true;
     }
 
-    // Starting with FF61, StyleRuleActor provides an accessor to signal if the property
-    // name is valid. If we don't have this, assume the name is valid. In use, rely on
-    // isValid() as a guard against false positives.
-    return (this.rule.domRule.declarations[selfIndex].isNameValid !== undefined)
-      ? this.rule.domRule.declarations[selfIndex].isNameValid
-      : true;
+    return this.rule.domRule.declarations[selfIndex].isNameValid;
   }
 }
 
 module.exports = TextProperty;
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -7748,22 +7748,21 @@ static bool ReadStringCommon(JSContext* 
 
       // Determine the length.
       UniqueTwoByteChars dst(
           inflateUTF8(cx, JS::UTF8Chars(bytes, length), &length).get());
       if (!dst) {
         return false;
       }
 
-      result = JS_NewUCString(cx, dst.get(), length);
+      result = JS_NewUCString(cx, std::move(dst), length);
       if (!result) {
         return false;
       }
 
-      mozilla::Unused << dst.release();
       break;
     }
     case TYPE_int16_t:
     case TYPE_uint16_t:
     case TYPE_short:
     case TYPE_unsigned_short:
     case TYPE_char16_t: {
       char16_t* chars = static_cast<char16_t*>(data);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/regress/bug1533204.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: helperThreadCount() === 0
+enableOsiPointRegisterChecks();
+evalInWorker(`
+function DiagModule(stdlib, foreign) {
+    "use asm";
+    function diag() {
+        while(1) {}
+    }
+    return {};
+`);
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -6287,17 +6287,17 @@ bool CodeGenerator::generateBody() {
       JitSpewFin(JitSpew_Codegen);
 #endif
 
       if (counts) {
         blockCounts->visitInstruction(*iter);
       }
 
 #ifdef CHECK_OSIPOINT_REGISTERS
-      if (iter->safepoint()) {
+      if (iter->safepoint() && !gen->compilingWasm()) {
         resetOsiPointRegs(iter->safepoint());
       }
 #endif
 
       if (iter->mirRaw()) {
         // Only add instructions that have a tracked inline script tree.
         if (iter->mirRaw()->trackedTree()) {
           if (!addNativeToBytecodeEntry(iter->mirRaw()->trackedSite())) {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4273,36 +4273,38 @@ JS_PUBLIC_API JSString* JS_AtomizeAndPin
                                                 size_t length) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   JSAtom* atom = Atomize(cx, s, length, PinAtom);
   MOZ_ASSERT_IF(atom, JS_StringHasBeenPinned(cx, atom));
   return atom;
 }
 
-JS_PUBLIC_API JSString* JS_NewLatin1String(JSContext* cx, JS::Latin1Char* chars,
-                                           size_t length) {
+JS_PUBLIC_API JSString* JS_NewLatin1String(
+    JSContext* cx, js::UniquePtr<JS::Latin1Char[], JS::FreePolicy> chars,
+    size_t length) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
-  return NewString(cx, chars, length);
-}
-
-JS_PUBLIC_API JSString* JS_NewUCString(JSContext* cx, char16_t* chars,
+  return NewString(cx, std::move(chars), length);
+}
+
+JS_PUBLIC_API JSString* JS_NewUCString(JSContext* cx,
+                                       JS::UniqueTwoByteChars chars,
                                        size_t length) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
-  return NewString(cx, chars, length);
+  return NewString(cx, std::move(chars), length);
 }
 
 JS_PUBLIC_API JSString* JS_NewUCStringDontDeflate(JSContext* cx,
-                                                  char16_t* chars,
+                                                  JS::UniqueTwoByteChars chars,
                                                   size_t length) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
-  return NewStringDontDeflate(cx, chars, length);
+  return NewStringDontDeflate(cx, std::move(chars), length);
 }
 
 JS_PUBLIC_API JSString* JS_NewUCStringCopyN(JSContext* cx, const char16_t* s,
                                             size_t n) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   if (!n) {
     return cx->names().empty;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2599,26 +2599,26 @@ extern JS_PUBLIC_API JSString* JS_Atomiz
 
 extern JS_PUBLIC_API JSString* JS_AtomizeAndPinStringN(JSContext* cx,
                                                        const char* s,
                                                        size_t length);
 
 extern JS_PUBLIC_API JSString* JS_AtomizeAndPinString(JSContext* cx,
                                                       const char* s);
 
-extern JS_PUBLIC_API JSString* JS_NewLatin1String(JSContext* cx,
-                                                  JS::Latin1Char* chars,
-                                                  size_t length);
-
-extern JS_PUBLIC_API JSString* JS_NewUCString(JSContext* cx, char16_t* chars,
+extern JS_PUBLIC_API JSString* JS_NewLatin1String(
+    JSContext* cx, js::UniquePtr<JS::Latin1Char[], JS::FreePolicy> chars,
+    size_t length);
+
+extern JS_PUBLIC_API JSString* JS_NewUCString(JSContext* cx,
+                                              JS::UniqueTwoByteChars chars,
                                               size_t length);
 
-extern JS_PUBLIC_API JSString* JS_NewUCStringDontDeflate(JSContext* cx,
-                                                         char16_t* chars,
-                                                         size_t length);
+extern JS_PUBLIC_API JSString* JS_NewUCStringDontDeflate(
+    JSContext* cx, JS::UniqueTwoByteChars chars, size_t length);
 
 extern JS_PUBLIC_API JSString* JS_NewUCStringCopyN(JSContext* cx,
                                                    const char16_t* s, size_t n);
 
 extern JS_PUBLIC_API JSString* JS_NewUCStringCopyZ(JSContext* cx,
                                                    const char16_t* s);
 
 extern JS_PUBLIC_API JSString* JS_AtomizeUCStringN(JSContext* cx,
--- a/js/src/vm/AtomsTable.h
+++ b/js/src/vm/AtomsTable.h
@@ -6,16 +6,18 @@
 
 /*
  * Implementation details of the atoms table.
  */
 
 #ifndef vm_AtomsTable_h
 #define vm_AtomsTable_h
 
+#include <type_traits>  // std::{enable_if,is_const}
+
 #include "js/GCHashTable.h"
 #include "js/TypeDecls.h"
 #include "vm/JSAtom.h"
 
 /*
  * The atoms table is a mapping from strings to JSAtoms that supports concurrent
  * access and incremental sweeping.
  *
@@ -157,22 +159,32 @@ class AtomsTable {
     JSAtom* front() const;
     void removeFront();
     void popFront();
   };
 
   ~AtomsTable();
   bool init();
 
-  template <typename CharT>
+  template <typename Chars>
   MOZ_ALWAYS_INLINE JSAtom* atomizeAndCopyChars(
-      JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
+      JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
       const mozilla::Maybe<uint32_t>& indexValue,
       const AtomHasher::Lookup& lookup);
 
+  template <typename CharT, typename = typename std::enable_if<
+                                !std::is_const<CharT>::value>::type>
+  MOZ_ALWAYS_INLINE JSAtom* atomizeAndCopyChars(
+      JSContext* cx, CharT* chars, size_t length, PinningBehavior pin,
+      const mozilla::Maybe<uint32_t>& indexValue,
+      const AtomHasher::Lookup& lookup) {
+    return atomizeAndCopyChars(cx, const_cast<const CharT*>(chars), length, pin,
+                               indexValue, lookup);
+  }
+
   void pinExistingAtom(JSContext* cx, JSAtom* atom);
 
   void tracePinnedAtoms(JSTracer* trc, const AutoAccessAtomsZone& access);
 
   // Sweep all atoms non-incrementally.
   void sweepAll(JSRuntime* rt);
 
   bool startIncrementalSweep();
--- a/js/src/vm/JSAtom.cpp
+++ b/js/src/vm/JSAtom.cpp
@@ -55,19 +55,20 @@ extern bool GetUTF8AtomizationData(JSCon
                                    size_t* outlen,
                                    JS::SmallestEncoding* encoding,
                                    HashNumber* hashNum);
 
 struct js::AtomHasher::Lookup {
   union {
     const JS::Latin1Char* latin1Chars;
     const char16_t* twoByteChars;
+    LittleEndianChars littleEndianChars;
     const char* utf8Bytes;
   };
-  enum { TwoByteChar, Latin1, UTF8, WTF8 } type;
+  enum { TwoByteChar, LittleEndianTwoByte, Latin1, UTF8, WTF8 } type;
   size_t length;
   size_t byteLength;
   const JSAtom* atom; /* Optional. */
   JS::AutoCheckCannotGC nogc;
 
   HashNumber hash;
 
   MOZ_ALWAYS_INLINE Lookup(const char* utf8Bytes, size_t byteLen, size_t length,
@@ -102,37 +103,55 @@ struct js::AtomHasher::Lookup {
       latin1Chars = atom->latin1Chars(nogc);
       MOZ_ASSERT(mozilla::HashString(latin1Chars, length) == hash);
     } else {
       MOZ_ASSERT(type == TwoByteChar);
       twoByteChars = atom->twoByteChars(nogc);
       MOZ_ASSERT(mozilla::HashString(twoByteChars, length) == hash);
     }
   }
+
+  MOZ_ALWAYS_INLINE Lookup(LittleEndianChars chars, size_t length)
+      : littleEndianChars(chars),
+        type(LittleEndianTwoByte),
+        length(length),
+        atom(nullptr),
+        hash(mozilla::HashStringKnownLength(chars, length)) {}
 };
 
 inline HashNumber js::AtomHasher::hash(const Lookup& l) { return l.hash; }
 
 MOZ_ALWAYS_INLINE bool js::AtomHasher::match(const AtomStateEntry& entry,
                                              const Lookup& lookup) {
   JSAtom* key = entry.asPtrUnbarriered();
   if (lookup.atom) {
     return lookup.atom == key;
   }
   if (key->length() != lookup.length || key->hash() != lookup.hash) {
     return false;
   }
 
+  auto EqualsLittleEndianChars = [&lookup](auto keyChars) {
+    for (size_t i = 0, len = lookup.length; i < len; i++) {
+      if (keyChars[i] != lookup.littleEndianChars[i]) {
+        return false;
+      }
+    }
+    return true;
+  };
+
   if (key->hasLatin1Chars()) {
     const Latin1Char* keyChars = key->latin1Chars(lookup.nogc);
     switch (lookup.type) {
       case Lookup::Latin1:
         return mozilla::ArrayEqual(keyChars, lookup.latin1Chars, lookup.length);
       case Lookup::TwoByteChar:
         return EqualChars(keyChars, lookup.twoByteChars, lookup.length);
+      case Lookup::LittleEndianTwoByte:
+        return EqualsLittleEndianChars(keyChars);
       case Lookup::UTF8: {
         JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength);
         return UTF8OrWTF8EqualsChars(utf8, keyChars);
       }
       case Lookup::WTF8: {
         JS::WTF8Chars wtf8(lookup.utf8Bytes, lookup.byteLength);
         return UTF8OrWTF8EqualsChars(wtf8, keyChars);
       }
@@ -140,16 +159,18 @@ MOZ_ALWAYS_INLINE bool js::AtomHasher::m
   }
 
   const char16_t* keyChars = key->twoByteChars(lookup.nogc);
   switch (lookup.type) {
     case Lookup::Latin1:
       return EqualChars(lookup.latin1Chars, keyChars, lookup.length);
     case Lookup::TwoByteChar:
       return mozilla::ArrayEqual(keyChars, lookup.twoByteChars, lookup.length);
+    case Lookup::LittleEndianTwoByte:
+      return EqualsLittleEndianChars(keyChars);
     case Lookup::UTF8: {
       JS::UTF8Chars utf8(lookup.utf8Bytes, lookup.byteLength);
       return UTF8OrWTF8EqualsChars(utf8, keyChars);
     }
     case Lookup::WTF8: {
       JS::WTF8Chars wtf8(lookup.utf8Bytes, lookup.byteLength);
       return UTF8OrWTF8EqualsChars(wtf8, keyChars);
     }
@@ -602,52 +623,49 @@ bool JSRuntime::initMainAtomsTables(JSCo
   permanentAtomsDuringInit_ = nullptr;
 
   // Initialize the main atoms table.
   MOZ_ASSERT(!atoms_);
   atoms_ = js_new<AtomsTable>();
   return atoms_ && atoms_->init();
 }
 
-template <typename CharT>
-MOZ_NEVER_INLINE static JSAtom* PermanentlyAtomizeAndCopyChars(
-    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, const CharT* tbchars,
-    size_t length, const Maybe<uint32_t>& indexValue,
-    const AtomHasher::Lookup& lookup);
+template <typename Chars>
+static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup(
+    JSContext* cx, Chars chars, size_t length, const AtomHasher::Lookup& lookup,
+    PinningBehavior pin, const Maybe<uint32_t>& indexValue);
 
-template <typename CharT>
-MOZ_ALWAYS_INLINE static JSAtom* AllocateNewAtom(
-    JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
+template <typename CharT, typename = typename std::enable_if<
+                              !std::is_const<CharT>::value>::type>
+static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup(
+    JSContext* cx, CharT* chars, size_t length,
+    const AtomHasher::Lookup& lookup, PinningBehavior pin,
+    const Maybe<uint32_t>& indexValue) {
+  return AtomizeAndCopyCharsFromLookup(cx, const_cast<const CharT*>(chars),
+                                       length, lookup, pin, indexValue);
+}
+
+template <typename Chars>
+static MOZ_NEVER_INLINE JSAtom* PermanentlyAtomizeAndCopyChars(
+    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, Chars chars, size_t length,
     const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup);
 
-template <typename CharT>
-MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup(
-    JSContext* cx, const CharT* tbchars, size_t length,
-    const AtomHasher::Lookup& lookup, PinningBehavior pin,
-    const Maybe<uint32_t>& indexValue);
-
-/* |tbchars| must not point into an inline or short string. */
-template <typename CharT>
-MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyChars(
-    JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
-    const Maybe<uint32_t>& indexValue) {
-  if (JSAtom* s = cx->staticStrings().lookup(tbchars, length)) {
-    return s;
-  }
-
-  AtomHasher::Lookup lookup(tbchars, length);
-  return AtomizeAndCopyCharsFromLookup(cx, tbchars, length, lookup, pin,
-                                       indexValue);
+template <typename CharT, typename = typename std::enable_if<
+                              !std::is_const<CharT>::value>::type>
+static JSAtom* PermanentlyAtomizeAndCopyChars(
+    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, CharT* chars, size_t length,
+    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
+  return PermanentlyAtomizeAndCopyChars(
+      cx, zonePtr, const_cast<const CharT*>(chars), length, indexValue, lookup);
 }
 
-template <typename CharT>
-MOZ_ALWAYS_INLINE static JSAtom* AtomizeAndCopyCharsFromLookup(
-    JSContext* cx, const CharT* tbchars, size_t length,
-    const AtomHasher::Lookup& lookup, PinningBehavior pin,
-    const Maybe<uint32_t>& indexValue) {
+template <typename Chars>
+static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyCharsFromLookup(
+    JSContext* cx, Chars chars, size_t length, const AtomHasher::Lookup& lookup,
+    PinningBehavior pin, const Maybe<uint32_t>& indexValue) {
   // Try the per-Zone cache first. If we find the atom there we can avoid the
   // atoms lock, the markAtom call, and the multiple HashSet lookups below.
   // We don't use the per-Zone cache if we want a pinned atom: handling that
   // is more complicated and pinning atoms is relatively uncommon.
   Zone* zone = cx->zone();
   Maybe<AtomSet::AddPtr> zonePtr;
   if (MOZ_LIKELY(zone && pin == DoNotPinAtom)) {
     zonePtr.emplace(zone->atomCache().lookupForAdd(lookup));
@@ -660,17 +678,17 @@ MOZ_ALWAYS_INLINE static JSAtom* Atomize
       return atom;
     }
   }
 
   // This function can be called during initialization, while the permanent
   // atoms table is being created. In this case all atoms created are added to
   // the permanent atoms table.
   if (!cx->permanentAtomsPopulated()) {
-    return PermanentlyAtomizeAndCopyChars(cx, zonePtr, tbchars, length,
+    return PermanentlyAtomizeAndCopyChars(cx, zonePtr, chars, length,
                                           indexValue, lookup);
   }
 
   AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup);
   if (pp) {
     JSAtom* atom = pp->asPtr(cx);
     if (zonePtr && MOZ_UNLIKELY(!zone->atomCache().add(
                        *zonePtr, AtomStateEntry(atom, false)))) {
@@ -682,17 +700,17 @@ MOZ_ALWAYS_INLINE static JSAtom* Atomize
   }
 
   // Validate the length before taking an atoms partition lock, as throwing an
   // exception here may reenter this code.
   if (MOZ_UNLIKELY(!JSString::validateLength(cx, length))) {
     return nullptr;
   }
 
-  JSAtom* atom = cx->atoms().atomizeAndCopyChars(cx, tbchars, length, pin,
+  JSAtom* atom = cx->atoms().atomizeAndCopyChars(cx, chars, length, pin,
                                                  indexValue, lookup);
   if (!atom) {
     return nullptr;
   }
 
   if (MOZ_UNLIKELY(!cx->atomMarking().inlinedMarkAtomFallible(cx, atom))) {
     ReportOutOfMemory(cx);
     return nullptr;
@@ -702,19 +720,33 @@ MOZ_ALWAYS_INLINE static JSAtom* Atomize
                      *zonePtr, AtomStateEntry(atom, false)))) {
     ReportOutOfMemory(cx);
     return nullptr;
   }
 
   return atom;
 }
 
-template <typename CharT>
+template <typename Chars>
+static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom(
+    JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
+    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup);
+
+template <typename CharT, typename = typename std::enable_if<
+                              !std::is_const<CharT>::value>::type>
+static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom(
+    JSContext* cx, CharT* chars, size_t length, PinningBehavior pin,
+    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
+  return AllocateNewAtom(cx, const_cast<const CharT*>(chars), length, pin,
+                         indexValue, lookup);
+}
+
+template <typename Chars>
 MOZ_ALWAYS_INLINE JSAtom* AtomsTable::atomizeAndCopyChars(
-    JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
+    JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
     const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
   Partition& part = *partitions[getPartitionIndex(lookup)];
   AutoLock lock(cx->runtime(), part.lock);
 
   AtomSet& atoms = part.atoms;
   AtomSet* atomsAddedWhileSweeping = part.atomsAddedWhileSweeping;
   AtomSet::AddPtr p;
 
@@ -741,17 +773,17 @@ MOZ_ALWAYS_INLINE JSAtom* AtomsTable::at
     JSAtom* atom = p->asPtr(cx);
     if (pin && !atom->isPinned()) {
       atom->setPinned();
       p->setPinned(true);
     }
     return atom;
   }
 
-  JSAtom* atom = AllocateNewAtom(cx, tbchars, length, pin, indexValue, lookup);
+  JSAtom* atom = AllocateNewAtom(cx, chars, length, pin, indexValue, lookup);
   if (!atom) {
     return nullptr;
   }
 
   // We have held the lock since looking up p, and the operations we've done
   // since then can't GC; therefore the atoms table has not been modified and
   // p is still valid.
   AtomSet* addSet =
@@ -759,41 +791,46 @@ MOZ_ALWAYS_INLINE JSAtom* AtomsTable::at
   if (MOZ_UNLIKELY(!addSet->add(p, AtomStateEntry(atom, bool(pin))))) {
     ReportOutOfMemory(cx); /* SystemAllocPolicy does not report OOM. */
     return nullptr;
   }
 
   return atom;
 }
 
-template JSAtom* AtomizeAndCopyChars(JSContext* cx, const char16_t* tbchars,
-                                     size_t length, PinningBehavior pin,
-                                     const Maybe<uint32_t>& indexValue);
+/* |chars| must not point into an inline or short string. */
+template <typename CharT>
+static MOZ_ALWAYS_INLINE JSAtom* AtomizeAndCopyChars(
+    JSContext* cx, const CharT* chars, size_t length, PinningBehavior pin,
+    const Maybe<uint32_t>& indexValue) {
+  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
+    return s;
+  }
 
-template JSAtom* AtomizeAndCopyChars(JSContext* cx, const Latin1Char* tbchars,
-                                     size_t length, PinningBehavior pin,
-                                     const Maybe<uint32_t>& indexValue);
+  AtomHasher::Lookup lookup(chars, length);
+  return AtomizeAndCopyCharsFromLookup(cx, chars, length, lookup, pin,
+                                       indexValue);
+}
 
-template <typename CharT>
-MOZ_NEVER_INLINE static JSAtom* PermanentlyAtomizeAndCopyChars(
-    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, const CharT* tbchars,
-    size_t length, const Maybe<uint32_t>& indexValue,
-    const AtomHasher::Lookup& lookup) {
+template <typename Chars>
+static MOZ_NEVER_INLINE JSAtom* PermanentlyAtomizeAndCopyChars(
+    JSContext* cx, Maybe<AtomSet::AddPtr>& zonePtr, Chars chars, size_t length,
+    const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
   MOZ_ASSERT(!cx->permanentAtomsPopulated());
   MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
 
   JSRuntime* rt = cx->runtime();
   AtomSet& atoms = *rt->permanentAtomsDuringInit();
   AtomSet::AddPtr p = atoms.lookupForAdd(lookup);
   if (p) {
     return p->asPtr(cx);
   }
 
   JSAtom* atom =
-      AllocateNewAtom(cx, tbchars, length, DoNotPinAtom, indexValue, lookup);
+      AllocateNewAtom(cx, chars, length, DoNotPinAtom, indexValue, lookup);
   if (!atom) {
     return nullptr;
   }
 
   atom->morphIntoPermanentAtom();
 
   // We are single threaded at this point, and the operations we've done since
   // then can't GC; therefore the atoms table has not been modified and p is
@@ -820,23 +857,30 @@ struct AtomizeUTF8OrWTF8CharsWrapper {
   AtomizeUTF8OrWTF8CharsWrapper(const CharsT& chars,
                                 JS::SmallestEncoding minEncode)
       : utf8(chars), encoding(minEncode) {}
 };
 
 // MakeFlatStringForAtomization has 4 variants.
 // This is used by Latin1Char and char16_t.
 template <typename CharT>
-MOZ_ALWAYS_INLINE static JSFlatString* MakeFlatStringForAtomization(
-    JSContext* cx, const CharT* tbchars, size_t length) {
-  return NewStringCopyN<NoGC>(cx, tbchars, length);
+static MOZ_ALWAYS_INLINE JSFlatString* MakeFlatStringForAtomization(
+    JSContext* cx, const CharT* chars, size_t length) {
+  return NewStringCopyN<NoGC>(cx, chars, length);
+}
+
+// MakeFlatStringForAtomization has one further variant -- a non-template
+// overload accepting LittleEndianChars.
+static MOZ_ALWAYS_INLINE JSFlatString* MakeFlatStringForAtomization(
+    JSContext* cx, LittleEndianChars chars, size_t length) {
+  return NewStringFromLittleEndianNoGC(cx, chars, length);
 }
 
 template <typename CharT, typename WrapperT>
-MOZ_ALWAYS_INLINE static JSFlatString* MakeUTF8AtomHelper(JSContext* cx,
+static MOZ_ALWAYS_INLINE JSFlatString* MakeUTF8AtomHelper(JSContext* cx,
                                                           const WrapperT* chars,
                                                           size_t length) {
   if (JSInlineString::lengthFits<CharT>(length)) {
     CharT* storage;
     JSInlineString* str = AllocateInlineString<NoGC>(cx, length, &storage);
     if (!str) {
       return nullptr;
     }
@@ -853,50 +897,42 @@ MOZ_ALWAYS_INLINE static JSFlatString* M
   UniquePtr<CharT[], JS::FreePolicy> newStr(js_pod_malloc<CharT>(length + 1));
   if (!newStr) {
     return nullptr;
   }
 
   InflateUTF8CharsToBufferAndTerminate(chars->utf8, newStr.get(), length,
                                        chars->encoding);
 
-  JSFlatString* str = JSFlatString::new_<NoGC>(cx, newStr.get(), length);
-  if (!str) {
-    return nullptr;
-  }
-
-  mozilla::Unused << newStr.release();
-  return str;
+  return JSFlatString::new_<NoGC>(cx, std::move(newStr), length);
 }
 
 // Another 2 variants of MakeFlatStringForAtomization.
 // This is used by AtomizeUTF8OrWTF8CharsWrapper with UTF8Chars or WTF8Chars.
 template <typename InputCharsT>
-MOZ_ALWAYS_INLINE
-    /* static */ JSFlatString*
-    MakeFlatStringForAtomization(
-        JSContext* cx, const AtomizeUTF8OrWTF8CharsWrapper<InputCharsT>* chars,
-        size_t length) {
+/* static */ MOZ_ALWAYS_INLINE JSFlatString* MakeFlatStringForAtomization(
+    JSContext* cx, const AtomizeUTF8OrWTF8CharsWrapper<InputCharsT>* chars,
+    size_t length) {
   if (length == 0) {
     return cx->emptyString();
   }
 
   if (chars->encoding == JS::SmallestEncoding::UTF16) {
     return MakeUTF8AtomHelper<char16_t>(cx, chars, length);
   }
   return MakeUTF8AtomHelper<JS::Latin1Char>(cx, chars, length);
 }
 
-template <typename CharT>
-MOZ_ALWAYS_INLINE static JSAtom* AllocateNewAtom(
-    JSContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin,
+template <typename Chars>
+static MOZ_ALWAYS_INLINE JSAtom* AllocateNewAtom(
+    JSContext* cx, Chars chars, size_t length, PinningBehavior pin,
     const Maybe<uint32_t>& indexValue, const AtomHasher::Lookup& lookup) {
   AutoAllocInAtomsZone ac(cx);
 
-  JSFlatString* flat = MakeFlatStringForAtomization(cx, tbchars, length);
+  JSFlatString* flat = MakeFlatStringForAtomization(cx, chars, length);
   if (!flat) {
     // Grudgingly forgo last-ditch GC. The alternative would be to release
     // the lock, manually GC here, and retry from the top. If you fix this,
     // please also fix or comment the similar case in Symbol::new_.
     ReportOutOfMemory(cx);
     return nullptr;
   }
 
@@ -1121,16 +1157,32 @@ JSAtom* js::ToAtom(JSContext* cx,
   }
   return atom;
 }
 
 template JSAtom* js::ToAtom<CanGC>(JSContext* cx, HandleValue v);
 
 template JSAtom* js::ToAtom<NoGC>(JSContext* cx, const Value& v);
 
+static JSAtom* AtomizeLittleEndianTwoByteChars(JSContext* cx,
+                                               const uint8_t* leTwoByte,
+                                               size_t length) {
+  CHECK_THREAD(cx);
+
+  LittleEndianChars chars(leTwoByte);
+
+  if (JSAtom* s = cx->staticStrings().lookup(chars, length)) {
+    return s;
+  }
+
+  AtomHasher::Lookup lookup(chars, length);
+  return AtomizeAndCopyCharsFromLookup(cx, chars, length, lookup, DoNotPinAtom,
+                                       Nothing());
+}
+
 template <XDRMode mode>
 XDRResult js::XDRAtom(XDRState<mode>* xdr, MutableHandleAtom atomp) {
   bool latin1 = false;
   uint32_t length = 0;
   uint32_t lengthAndEncoding = 0;
   if (mode == XDR_ENCODE) {
     static_assert(JSString::MAX_LENGTH <= INT32_MAX,
                   "String length must fit in 31 bits");
@@ -1165,61 +1217,23 @@ XDRResult js::XDRAtom(XDRState<mode>* xd
     if (length) {
       const uint8_t* ptr;
       size_t nbyte = length * sizeof(Latin1Char);
       MOZ_TRY(xdr->peekData(&ptr, nbyte));
       chars = reinterpret_cast<const Latin1Char*>(ptr);
     }
     atom = AtomizeChars(cx, chars, length);
   } else {
-#if MOZ_LITTLE_ENDIAN
-    /* Directly access the little endian chars in the XDR buffer. */
-    const char16_t* chars = nullptr;
+    const uint8_t* twoByteCharsLE = nullptr;
     if (length) {
-      // In the |mode == XDR_ENCODE| case above, when |nchars > 0|,
-      // |XDRState::codeChars(char16_t*, size_t nchars)| will align the
-      // buffer.  This code never calls that function, but it must act
-      // *as if* it had, so we must align manually here.
-      MOZ_TRY(xdr->codeAlign(sizeof(char16_t)));
-
-      const uint8_t* ptr;
       size_t nbyte = length * sizeof(char16_t);
-      MOZ_TRY(xdr->peekData(&ptr, nbyte));
-      MOZ_ASSERT(reinterpret_cast<uintptr_t>(ptr) % sizeof(char16_t) == 0,
-                 "non-aligned buffer during JSAtom decoding");
-      chars = reinterpret_cast<const char16_t*>(ptr);
-    }
-    atom = AtomizeChars(cx, chars, length);
-#else
-    /*
-     * We must copy chars to a temporary buffer to convert between little and
-     * big endian data.
-     */
-    char16_t* chars;
-    char16_t stackChars[256];
-    UniqueTwoByteChars heapChars;
-    if (length <= ArrayLength(stackChars)) {
-      chars = stackChars;
-    } else {
-      /*
-       * This is very uncommon. Don't use the tempLifoAlloc arena for this as
-       * most allocations here will be bigger than tempLifoAlloc's default
-       * chunk size.
-       */
-      heapChars.reset(cx->pod_malloc<char16_t>(length));
-      if (!heapChars) {
-        return xdr->fail(JS::TranscodeResult_Throw);
-      }
-
-      chars = heapChars.get();
+      MOZ_TRY(xdr->peekData(&twoByteCharsLE, nbyte));
     }
 
-    MOZ_TRY(xdr->codeChars(chars, length));
-    atom = AtomizeChars(cx, chars, length);
-#endif /* !MOZ_LITTLE_ENDIAN */
+    atom = AtomizeLittleEndianTwoByteChars(cx, twoByteCharsLE, length);
   }
 
   if (!atom) {
     return xdr->fail(JS::TranscodeResult_Throw);
   }
   atomp.set(atom);
   return Ok();
 }
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -589,17 +589,16 @@ XDRResult js::XDRInterpretedFunction(XDR
     // mirror the scope chain.
     MOZ_ASSERT_IF(fun->isSingleton() && !((lazy && lazy->hasBeenCloned()) ||
                                           (script && script->hasBeenCloned())),
                   fun->environment() == nullptr);
   }
 
   // Everything added below can substituted by the non-lazy-script version of
   // this function later.
-  MOZ_TRY(xdr->codeAlign(sizeof(js::XDRAlignment)));
   js::AutoXDRTree funTree(xdr, xdr->getTreeKey(fun));
 
   MOZ_TRY(xdr->codeUint32(&firstword));
 
   if (firstword & HasAtom) {
     MOZ_TRY(XDRAtom(xdr, &atom));
   }
   MOZ_TRY(xdr->codeUint32(&flagsword));
@@ -653,20 +652,16 @@ XDRResult js::XDRInterpretedFunction(XDR
       return xdr->fail(JS::TranscodeResult_Throw);
     }
     objp.set(fun);
   }
 
   // Verify marker at end of function to detect buffer trunction.
   MOZ_TRY(xdr->codeMarker(0x9E35CA1F));
 
-  // Required by AutoXDRTree to copy & paste snipet of sub-trees while keeping
-  // the alignment.
-  MOZ_TRY(xdr->codeAlign(sizeof(js::XDRAlignment)));
-
   return Ok();
 }
 
 template XDRResult js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*,
                                               HandleScope,
                                               HandleScriptSourceObject,
                                               MutableHandleFunction);
 
--- a/js/src/vm/StringType-inl.h
+++ b/js/src/vm/StringType-inl.h
@@ -11,16 +11,17 @@
 
 #include "mozilla/PodOperations.h"
 #include "mozilla/Range.h"
 
 #include "gc/Allocator.h"
 #include "gc/FreeOp.h"
 #include "gc/Marking.h"
 #include "gc/StoreBuffer.h"
+#include "js/UniquePtr.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 
 #include "gc/StoreBuffer-inl.h"
 
 namespace js {
 
 // Allocate a thin inline string if possible, and a fat inline string if not.
@@ -81,35 +82,44 @@ static MOZ_ALWAYS_INLINE JSInlineString*
   }
 
   JS::AutoCheckCannotGC nogc;
   mozilla::PodCopy(chars, base->chars<CharT>(nogc) + start, length);
   chars[length] = 0;
   return s;
 }
 
-template <typename CharT>
-static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString(
-    JSContext* cx, const CharT* chars, size_t n) {
+template <typename Chars>
+static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString(JSContext* cx,
+                                                              Chars chars,
+                                                              size_t n) {
   // Measurements on popular websites indicate empty strings are pretty common
   // and most strings with length 1 or 2 are in the StaticStrings table. For
   // length 3 strings that's only about 1%, so we check n <= 2.
   if (n <= 2) {
     if (n == 0) {
       return cx->emptyString();
     }
 
     if (JSFlatString* str = cx->staticStrings().lookup(chars, n)) {
       return str;
     }
   }
 
   return nullptr;
 }
 
+template <typename CharT, typename = typename std::enable_if<
+                              !std::is_const<CharT>::value>::type>
+static MOZ_ALWAYS_INLINE JSFlatString* TryEmptyOrStaticString(JSContext* cx,
+                                                              CharT* chars,
+                                                              size_t n) {
+  return TryEmptyOrStaticString(cx, const_cast<const CharT*>(chars), n);
+}
+
 } /* namespace js */
 
 MOZ_ALWAYS_INLINE bool JSString::validateLength(JSContext* maybecx,
                                                 size_t length) {
   if (MOZ_UNLIKELY(length > JSString::MAX_LENGTH)) {
     js::ReportAllocationOverflow(maybecx);
     return false;
   }
@@ -244,19 +254,19 @@ MOZ_ALWAYS_INLINE void JSFlatString::ini
 
 MOZ_ALWAYS_INLINE void JSFlatString::init(const JS::Latin1Char* chars,
                                           size_t length) {
   setLengthAndFlags(length, INIT_FLAT_FLAGS | LATIN1_CHARS_BIT);
   d.s.u2.nonInlineCharsLatin1 = chars;
 }
 
 template <js::AllowGC allowGC, typename CharT>
-MOZ_ALWAYS_INLINE JSFlatString* JSFlatString::new_(JSContext* cx,
-                                                   const CharT* chars,
-                                                   size_t length) {
+MOZ_ALWAYS_INLINE JSFlatString* JSFlatString::new_(
+    JSContext* cx, js::UniquePtr<CharT[], JS::FreePolicy> chars,
+    size_t length) {
   MOZ_ASSERT(chars[length] == CharT(0));
 
   if (!validateLength(cx, length)) {
     return nullptr;
   }
 
   JSFlatString* str;
   if (cx->zone()->isAtomsZone()) {
@@ -264,31 +274,29 @@ MOZ_ALWAYS_INLINE JSFlatString* JSFlatSt
   } else {
     str = js::AllocateString<JSFlatString, allowGC>(cx, js::gc::DefaultHeap);
   }
   if (!str) {
     return nullptr;
   }
 
   if (!str->isTenured()) {
-    // The chars pointer is only considered to be handed over to this
-    // function on a successful return. If the following registration
-    // fails, the string is partially initialized and must be made valid,
-    // or its finalizer may attempt to free uninitialized memory.
-    void* ptr = const_cast<void*>(static_cast<const void*>(chars));
-    if (!cx->runtime()->gc.nursery().registerMallocedBuffer(ptr)) {
-      str->init((JS::Latin1Char*)nullptr, 0);
+    // If the following registration fails, the string is partially initialized
+    // and must be made valid, or its finalizer may attempt to free
+    // uninitialized memory.
+    if (!cx->runtime()->gc.nursery().registerMallocedBuffer(chars.get())) {
+      str->init(static_cast<JS::Latin1Char*>(nullptr), 0);
       if (allowGC) {
         ReportOutOfMemory(cx);
       }
       return nullptr;
     }
   }
 
-  str->init(chars, length);
+  str->init(chars.release(), length);
   return str;
 }
 
 inline js::PropertyName* JSFlatString::toPropertyName(JSContext* cx) {
 #ifdef DEBUG
   uint32_t dummy;
   MOZ_ASSERT(!isIndex(&dummy));
 #endif
--- a/js/src/vm/StringType.cpp
+++ b/js/src/vm/StringType.cpp
@@ -2,27 +2,29 @@
  * 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 "vm/StringType-inl.h"
 
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/Casting.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Unused.h"
 
-#include <algorithm>
+#include <algorithm>  // std::{all_of,copy_n,enable_if,is_const,move}
+#include <type_traits>  // std::is_unsigned
 
 #include "jsfriendapi.h"
 
 #include "frontend/BytecodeCompiler.h"
 #include "gc/GCInternals.h"
 #include "gc/Marking.h"
 #include "gc/Nursery.h"
 #include "js/CharacterEncoding.h"
@@ -35,16 +37,17 @@
 #include "vm/GeckoProfiler-inl.h"
 #include "vm/JSContext-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/Realm-inl.h"
 
 using namespace js;
 
 using mozilla::ArrayEqual;
+using mozilla::AssertedCast;
 using mozilla::IsAsciiDigit;
 using mozilla::IsNegativeZero;
 using mozilla::IsSame;
 using mozilla::PodCopy;
 using mozilla::RangedPtr;
 using mozilla::RoundUpPow2;
 using mozilla::Unused;
 
@@ -791,34 +794,88 @@ JSString* js::ConcatStrings(
 }
 
 template JSString* js::ConcatStrings<CanGC>(JSContext* cx, HandleString left,
                                             HandleString right);
 
 template JSString* js::ConcatStrings<NoGC>(JSContext* cx, JSString* const& left,
                                            JSString* const& right);
 
+/**
+ * Copy |src[0..length]| to |dest[0..length]| when copying doesn't narrow and
+ * therefore can't lose information.
+ */
+template <typename Dest, typename Source>
+static void FillAndTerminate(Dest* dest, Source src, size_t length) {
+  static_assert(sizeof(src[length]) <= sizeof(dest[length]),
+                "source → destination conversion must not narrow");
+
+  for (size_t i = 0; i < length; i++) {
+    auto srcChar = src[i];
+    static_assert(std::is_unsigned<decltype(srcChar)>::value &&
+                  std::is_unsigned<Dest>::value,
+                  "source/destination characters are unsigned for simplicity");
+    *dest++ = srcChar;
+  }
+
+  *dest = '\0';
+}
+
+template <typename Dest, typename Src,
+          typename = typename std::enable_if<!std::is_const<Src>::value>::type>
+static void FillAndTerminate(Dest* dest, Src* src, size_t length) {
+  FillAndTerminate(dest, const_cast<const Src*>(src), length);
+}
+
+/**
+ * Copy |src[0..length]| to |dest[0..length]| when copying *does* narrow, but
+ * the user guarantees every runtime |src[i]| value can be stored without change
+ * of value in |dest[i]|.
+ */
+template <typename Dest, typename Source>
+static void FillFromCompatibleAndTerminate(Dest* dest, Source src,
+                                           size_t length) {
+  static_assert(sizeof(src[length]) > sizeof(dest[length]),
+                "source → destination conversion must be narrowing");
+
+  for (size_t i = 0; i < length; i++) {
+    auto srcChar = src[i];
+    static_assert(std::is_unsigned<decltype(srcChar)>::value &&
+                  std::is_unsigned<Dest>::value,
+                  "source/destination characters are unsigned for simplicity");
+    *dest++ = AssertedCast<Dest>(src[i]);
+  }
+
+  *dest = '\0';
+}
+
+template <typename Dest, typename Src,
+          typename = typename std::enable_if<!std::is_const<Src>::value>::type>
+static void FillFromCompatibleAndTerminate(Dest* dest, Src* src,
+                                           size_t length) {
+  FillFromCompatibleAndTerminate(dest, const_cast<const Src*>(src), length);
+}
+
 template <typename CharT>
 JSFlatString* JSDependentString::undependInternal(JSContext* cx) {
   size_t n = length();
   auto s = cx->make_pod_array<CharT>(n + 1);
   if (!s) {
     return nullptr;
   }
 
   if (!isTenured()) {
     if (!cx->runtime()->gc.nursery().registerMallocedBuffer(s.get())) {
       ReportOutOfMemory(cx);
       return nullptr;
     }
   }
 
   AutoCheckCannotGC nogc;
-  PodCopy(s.get(), nonInlineChars<CharT>(nogc), n);
-  s[n] = '\0';
+  FillAndTerminate(s.get(), nonInlineChars<CharT>(nogc), n);
   setNonInlineChars<CharT>(s.release());
 
   /*
    * Transform *this into an undepended string so 'base' will remain rooted
    * for the benefit of any other dependent string that depends on *this.
    */
   if (IsSame<CharT, Latin1Char>::value) {
     setLengthAndFlags(n, UNDEPENDED_FLAGS | LATIN1_CHARS_BIT);
@@ -1321,53 +1378,50 @@ T* AutoStableStringChars::allocOwnChars(
 
 bool AutoStableStringChars::copyAndInflateLatin1Chars(
     JSContext* cx, HandleLinearString linearString) {
   char16_t* chars = allocOwnChars<char16_t>(cx, linearString->length() + 1);
   if (!chars) {
     return false;
   }
 
-  CopyAndInflateChars(chars, linearString->rawLatin1Chars(),
-                      linearString->length());
-  chars[linearString->length()] = 0;
+  FillAndTerminate(chars, linearString->rawLatin1Chars(),
+                   linearString->length());
 
   state_ = TwoByte;
   twoByteChars_ = chars;
   s_ = linearString;
   return true;
 }
 
 bool AutoStableStringChars::copyLatin1Chars(JSContext* cx,
                                             HandleLinearString linearString) {
   size_t length = linearString->length();
   JS::Latin1Char* chars = allocOwnChars<JS::Latin1Char>(cx, length + 1);
   if (!chars) {
     return false;
   }
 
-  PodCopy(chars, linearString->rawLatin1Chars(), length);
-  chars[length] = 0;
+  FillAndTerminate(chars, linearString->rawLatin1Chars(), length);
 
   state_ = Latin1;
   latin1Chars_ = chars;
   s_ = linearString;
   return true;
 }
 
 bool AutoStableStringChars::copyTwoByteChars(JSContext* cx,
                                              HandleLinearString linearString) {
   size_t length = linearString->length();
   char16_t* chars = allocOwnChars<char16_t>(cx, length + 1);
   if (!chars) {
     return false;
   }
 
-  PodCopy(chars, linearString->rawTwoByteChars(), length);
-  chars[length] = 0;
+  FillAndTerminate(chars, linearString->rawTwoByteChars(), length);
 
   state_ = TwoByte;
   twoByteChars_ = chars;
   s_ = linearString;
   return true;
 }
 
 JSFlatString* JSString::ensureFlat(JSContext* cx) {
@@ -1397,18 +1451,17 @@ JSFlatString* JSExternalString::ensureFl
       ReportOutOfMemory(cx);
       return nullptr;
     }
   }
 
   // Copy the chars before finalizing the string.
   {
     AutoCheckCannotGC nogc;
-    PodCopy(s.get(), nonInlineChars<char16_t>(nogc), n);
-    s[n] = '\0';
+    FillAndTerminate(s.get(), nonInlineChars<char16_t>(nogc), n);
   }
 
   // Release the external chars.
   finalize(cx->runtime()->defaultFreeOp());
 
   // Transform the string into a non-external, flat string. Note that the
   // resulting string will still be in an AllocKind::EXTERNAL_STRING arena,
   // but will no longer be an external string.
@@ -1481,31 +1534,38 @@ static bool CanStoreCharsAsLatin1(const 
 
   return true;
 }
 
 static bool CanStoreCharsAsLatin1(const Latin1Char* s, size_t length) {
   MOZ_CRASH("Shouldn't be called for Latin1 chars");
 }
 
+static bool CanStoreCharsAsLatin1(LittleEndianChars chars, size_t length) {
+  for (size_t i = 0; i < length; i++) {
+    if (chars[i] > JSString::MAX_LATIN1_CHAR) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 template <AllowGC allowGC>
 static MOZ_ALWAYS_INLINE JSInlineString* NewInlineStringDeflated(
     JSContext* cx, mozilla::Range<const char16_t> chars) {
   size_t len = chars.length();
   Latin1Char* storage;
   JSInlineString* str = AllocateInlineString<allowGC>(cx, len, &storage);
   if (!str) {
     return nullptr;
   }
 
-  for (size_t i = 0; i < len; i++) {
-    MOZ_ASSERT(chars[i] <= JSString::MAX_LATIN1_CHAR);
-    storage[i] = Latin1Char(chars[i]);
-  }
-  storage[len] = '\0';
+  MOZ_ASSERT(CanStoreCharsAsLatin1(chars.begin().get(), len));
+  FillFromCompatibleAndTerminate(storage, chars, len);
   return str;
 }
 
 template <AllowGC allowGC>
 static JSFlatString* NewStringDeflated(JSContext* cx, const char16_t* s,
                                        size_t n) {
   if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n)) {
     return str;
@@ -1516,110 +1576,113 @@ static JSFlatString* NewStringDeflated(J
         cx, mozilla::Range<const char16_t>(s, n));
   }
 
   auto news = cx->make_pod_array<Latin1Char>(n + 1);
   if (!news) {
     return nullptr;
   }
 
-  for (size_t i = 0; i < n; i++) {
-    MOZ_ASSERT(s[i] <= JSString::MAX_LATIN1_CHAR);
-    news[i] = Latin1Char(s[i]);
-  }
-  news[n] = '\0';
+  MOZ_ASSERT(CanStoreCharsAsLatin1(s, n));
+  FillFromCompatibleAndTerminate(news.get(), s, n);
 
-  JSFlatString* str = JSFlatString::new_<allowGC>(cx, news.get(), n);
-  if (!str) {
-    return nullptr;
-  }
-
-  mozilla::Unused << news.release();
-  return str;
+  return JSFlatString::new_<allowGC>(cx, std::move(news), n);
 }
 
 template <AllowGC allowGC>
 static JSFlatString* NewStringDeflated(JSContext* cx, const Latin1Char* s,
                                        size_t n) {
   MOZ_CRASH("Shouldn't be called for Latin1 chars");
 }
 
-template <typename CharT>
-JSFlatString* js::NewStringDontDeflate(JSContext* cx, CharT* chars,
-                                       size_t length) {
-  if (JSFlatString* str = TryEmptyOrStaticString(cx, chars, length)) {
-    // Free |chars| because we're taking possession of it, but it's no
-    // longer needed because we use the static string instead.
-    js_free(chars);
-    return str;
-  }
+static JSFlatString* NewStringDeflatedFromLittleEndianNoGC(
+    JSContext* cx, LittleEndianChars chars, size_t length) {
+  MOZ_ASSERT(CanStoreCharsAsLatin1(chars, length));
 
-  if (JSInlineString::lengthFits<CharT>(length)) {
-    JSInlineString* str =
-        NewInlineString<CanGC>(cx, mozilla::Range<const CharT>(chars, length));
+  if (JSInlineString::lengthFits<Latin1Char>(length)) {
+    Latin1Char* storage;
+    JSInlineString* str = AllocateInlineString<NoGC>(cx, length, &storage);
     if (!str) {
       return nullptr;
     }
 
-    js_free(chars);
+    FillFromCompatibleAndTerminate(storage, chars, length);
     return str;
   }
 
-  return JSFlatString::new_<CanGC>(cx, chars, length);
+  auto news = cx->make_pod_array<Latin1Char>(length + 1);
+  if (!news) {
+    cx->recoverFromOutOfMemory();
+    return nullptr;
+  }
+
+  FillFromCompatibleAndTerminate(news.get(), chars, length);
+
+  return JSFlatString::new_<NoGC>(cx, std::move(news), length);
 }
 
-template JSFlatString* js::NewStringDontDeflate(JSContext* cx, char16_t* chars,
+template <typename CharT>
+JSFlatString* js::NewStringDontDeflate(JSContext* cx,
+                                       UniquePtr<CharT[], JS::FreePolicy> chars,
+                                       size_t length) {
+  if (JSFlatString* str = TryEmptyOrStaticString(cx, chars.get(), length)) {
+    return str;
+  }
+
+  if (JSInlineString::lengthFits<CharT>(length)) {
+    // |chars.get()| is safe because 1) |NewInlineString| necessarily *copies*,
+    // and 2) |chars| frees its contents only when this function returns.
+    return NewInlineString<CanGC>(
+        cx, mozilla::Range<const CharT>(chars.get(), length));
+  }
+
+  return JSFlatString::new_<CanGC>(cx, std::move(chars), length);
+}
+
+template JSFlatString* js::NewStringDontDeflate(JSContext* cx,
+                                                UniqueTwoByteChars chars,
                                                 size_t length);
 
 template JSFlatString* js::NewStringDontDeflate(JSContext* cx,
-                                                Latin1Char* chars,
+                                                UniqueLatin1Chars chars,
                                                 size_t length);
 
 template <typename CharT>
-JSFlatString* js::NewString(JSContext* cx, CharT* chars, size_t length) {
-  if (IsSame<CharT, char16_t>::value && CanStoreCharsAsLatin1(chars, length)) {
-    JSFlatString* s = NewStringDeflated<CanGC>(cx, chars, length);
-    if (!s) {
-      return nullptr;
-    }
-
-    // Free |chars| because we're taking possession of it but not using it.
-    js_free(chars);
-    return s;
+JSFlatString* js::NewString(JSContext* cx,
+                            UniquePtr<CharT[], JS::FreePolicy> chars,
+                            size_t length) {
+  if (IsSame<CharT, char16_t>::value &&
+      CanStoreCharsAsLatin1(chars.get(), length)) {
+    // Deflating copies from |chars.get()| and lets |chars| be freed on return.
+    return NewStringDeflated<CanGC>(cx, chars.get(), length);
   }
 
-  return NewStringDontDeflate(cx, chars, length);
+  return NewStringDontDeflate(cx, std::move(chars), length);
 }
 
-template JSFlatString* js::NewString(JSContext* cx, char16_t* chars,
+template JSFlatString* js::NewString(JSContext* cx, UniqueTwoByteChars chars,
                                      size_t length);
 
-template JSFlatString* js::NewString(JSContext* cx, Latin1Char* chars,
+template JSFlatString* js::NewString(JSContext* cx, UniqueLatin1Chars chars,
                                      size_t length);
 
 template <AllowGC allowGC, typename CharT>
 JSFlatString* js::NewStringDontDeflate(JSContext* cx,
                                        UniquePtr<CharT[], JS::FreePolicy> chars,
                                        size_t length) {
   if (JSFlatString* str = TryEmptyOrStaticString(cx, chars.get(), length)) {
     return str;
   }
 
   if (JSInlineString::lengthFits<CharT>(length)) {
     return NewInlineString<allowGC>(
         cx, mozilla::Range<const CharT>(chars.get(), length));
   }
 
-  JSFlatString* str = JSFlatString::new_<allowGC>(cx, chars.get(), length);
-  if (!str) {
-    return nullptr;
-  }
-
-  mozilla::Unused << chars.release();
-  return str;
+  return JSFlatString::new_<allowGC>(cx, std::move(chars), length);
 }
 
 template JSFlatString* js::NewStringDontDeflate<CanGC>(JSContext* cx,
                                                        UniqueTwoByteChars chars,
                                                        size_t length);
 
 template JSFlatString* js::NewStringDontDeflate<NoGC>(JSContext* cx,
                                                       UniqueTwoByteChars chars,
@@ -1677,26 +1740,19 @@ JSFlatString* NewStringCopyNDontDeflate(
   auto news = cx->make_pod_array<CharT>(n + 1);
   if (!news) {
     if (!allowGC) {
       cx->recoverFromOutOfMemory();
     }
     return nullptr;
   }
 
-  PodCopy(news.get(), s, n);
-  news[n] = 0;
+  FillAndTerminate(news.get(), s, n);
 
-  JSFlatString* str = JSFlatString::new_<allowGC>(cx, news.get(), n);
-  if (!str) {
-    return nullptr;
-  }
-
-  mozilla::Unused << news.release();
-  return str;
+  return JSFlatString::new_<allowGC>(cx, std::move(news), n);
 }
 
 template JSFlatString* NewStringCopyNDontDeflate<CanGC>(JSContext* cx,
                                                         const char16_t* s,
                                                         size_t n);
 
 template JSFlatString* NewStringCopyNDontDeflate<NoGC>(JSContext* cx,
                                                        const char16_t* s,
@@ -1705,16 +1761,40 @@ template JSFlatString* NewStringCopyNDon
 template JSFlatString* NewStringCopyNDontDeflate<CanGC>(JSContext* cx,
                                                         const Latin1Char* s,
                                                         size_t n);
 
 template JSFlatString* NewStringCopyNDontDeflate<NoGC>(JSContext* cx,
                                                        const Latin1Char* s,
                                                        size_t n);
 
+static JSFlatString* NewUndeflatedStringFromLittleEndianNoGC(
+    JSContext* cx, LittleEndianChars chars, size_t length) {
+  if (JSInlineString::lengthFits<char16_t>(length)) {
+    char16_t* storage;
+    JSInlineString* str = AllocateInlineString<NoGC>(cx, length, &storage);
+    if (!str) {
+      return nullptr;
+    }
+
+    FillAndTerminate(storage, chars, length);
+    return str;
+  }
+
+  auto news = cx->make_pod_array<char16_t>(length + 1);
+  if (!news) {
+    cx->recoverFromOutOfMemory();
+    return nullptr;
+  }
+
+  FillAndTerminate(news.get(), chars, length);
+
+  return JSFlatString::new_<NoGC>(cx, std::move(news), length);
+}
+
 JSFlatString* NewLatin1StringZ(JSContext* cx, UniqueChars chars) {
   size_t length = strlen(chars.get());
   UniqueLatin1Chars latin1(reinterpret_cast<Latin1Char*>(chars.release()));
   return NewString<CanGC>(cx, std::move(latin1), length);
 }
 
 template <AllowGC allowGC, typename CharT>
 JSFlatString* NewStringCopyN(JSContext* cx, const CharT* s, size_t n) {
@@ -1732,16 +1812,30 @@ template JSFlatString* NewStringCopyN<No
                                             size_t n);
 
 template JSFlatString* NewStringCopyN<CanGC>(JSContext* cx, const Latin1Char* s,
                                              size_t n);
 
 template JSFlatString* NewStringCopyN<NoGC>(JSContext* cx, const Latin1Char* s,
                                             size_t n);
 
+JSFlatString* NewStringFromLittleEndianNoGC(JSContext* cx,
+                                            LittleEndianChars chars,
+                                            size_t length) {
+  if (JSFlatString* str = TryEmptyOrStaticString(cx, chars, length)) {
+    return str;
+  }
+
+  if (CanStoreCharsAsLatin1(chars, length)) {
+    return NewStringDeflatedFromLittleEndianNoGC(cx, chars, length);
+  }
+
+  return NewUndeflatedStringFromLittleEndianNoGC(cx, chars, length);
+}
+
 template <js::AllowGC allowGC>
 JSFlatString* NewStringCopyUTF8N(JSContext* cx, const JS::UTF8Chars utf8) {
   JS::SmallestEncoding encoding = JS::FindSmallestEncoding(utf8);
   if (encoding == JS::SmallestEncoding::ASCII) {
     return NewStringCopyN<allowGC>(cx, utf8.begin().get(), utf8.length());
   }
 
   size_t length;
@@ -2088,18 +2182,17 @@ UniqueChars js::EncodeLatin1(JSContext* 
   }
 
   size_t len = str->length();
   Latin1Char* buf = cx->pod_malloc<Latin1Char>(len + 1);
   if (!buf) {
     return nullptr;
   }
 
-  mozilla::PodCopy(buf, linear->latin1Chars(nogc), len);
-  buf[len] = '\0';
+  FillAndTerminate(buf, linear->latin1Chars(nogc), len);
   return UniqueChars(reinterpret_cast<char*>(buf));
 }
 
 UniqueChars js::EncodeAscii(JSContext* cx, JSString* str) {
   JSLinearString* linear = str->ensureLinear(cx);
   if (!linear) {
     return nullptr;
   }
--- a/js/src/vm/StringType.h
+++ b/js/src/vm/StringType.h
@@ -6,16 +6,18 @@
 
 #ifndef vm_StringType_h
 #define vm_StringType_h
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Range.h"
 #include "mozilla/TextUtils.h"
 
+#include <type_traits>  // std::is_same
+
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 #include "builtin/String.h"
 #include "gc/Barrier.h"
 #include "gc/Cell.h"
 #include "gc/Heap.h"
 #include "gc/Nursery.h"
@@ -963,17 +965,18 @@ class JSFlatString : public JSLinearStri
   template <typename CharT>
   static bool isIndexSlow(const CharT* s, size_t length, uint32_t* indexp);
 
   void init(const char16_t* chars, size_t length);
   void init(const JS::Latin1Char* chars, size_t length);
 
  public:
   template <js::AllowGC allowGC, typename CharT>
-  static inline JSFlatString* new_(JSContext* cx, const CharT* chars,
+  static inline JSFlatString* new_(JSContext* cx,
+                                   js::UniquePtr<CharT[], JS::FreePolicy> chars,
                                    size_t length);
 
   inline bool isIndexSlow(uint32_t* indexp) const {
     MOZ_ASSERT(JSString::isFlat());
     JS::AutoCheckCannotGC nogc;
     if (hasLatin1Chars()) {
       const JS::Latin1Char* s = latin1Chars(nogc);
       return mozilla::IsAsciiDigit(*s) && isIndexSlow(s, length(), indexp);
@@ -1326,16 +1329,34 @@ MOZ_ALWAYS_INLINE JSAtom* JSFlatString::
   clearFlagBit(NON_ATOM_BIT);
   JSAtom* atom = &asAtom();
   atom->initHash(hash);
   return atom;
 }
 
 namespace js {
 
+/**
+ * An indexable characters class exposing unaligned, little-endian encoded
+ * char16_t data.
+ */
+class LittleEndianChars {
+ public:
+  explicit constexpr LittleEndianChars(const uint8_t* leTwoByte)
+      : current(leTwoByte) {}
+
+  constexpr char16_t operator[](size_t index) const {
+    size_t offset = index * sizeof(char16_t);
+    return (current[offset + 1] << 8) | current[offset];
+  }
+
+ private:
+  const uint8_t* current;
+};
+
 class StaticStrings {
  private:
   /* Bigger chars cannot be in a length-2 string. */
   static const size_t SMALL_CHAR_LIMIT = 128U;
   static const size_t NUM_SMALL_CHARS = 64U;
 
   JSAtom* length2StaticTable[NUM_SMALL_CHARS * NUM_SMALL_CHARS] = {};  // zeroes
 
@@ -1377,18 +1398,24 @@ class StaticStrings {
   inline JSLinearString* getUnitStringForElement(JSContext* cx, JSString* str,
                                                  size_t index);
 
   template <typename CharT>
   static bool isStatic(const CharT* chars, size_t len);
   static bool isStatic(JSAtom* atom);
 
   /* Return null if no static atom exists for the given (chars, length). */
-  template <typename CharT>
-  MOZ_ALWAYS_INLINE JSAtom* lookup(const CharT* chars, size_t length) {
+  template <typename Chars>
+  MOZ_ALWAYS_INLINE JSAtom* lookup(Chars chars, size_t length) {
+    static_assert(std::is_same<Chars, const Latin1Char*>::value ||
+                      std::is_same<Chars, const char16_t*>::value ||
+                      std::is_same<Chars, LittleEndianChars>::value,
+                  "for understandability, |chars| must be one of a few "
+                  "identified types");
+
     switch (length) {
       case 1: {
         char16_t c = chars[0];
         if (c < UNIT_STATIC_LIMIT) {
           return getUnit(c);
         }
         return nullptr;
       }
@@ -1418,16 +1445,29 @@ class StaticStrings {
           }
         }
         return nullptr;
     }
 
     return nullptr;
   }
 
+  template <typename CharT, typename = typename std::enable_if<
+                                std::is_same<CharT, char>::value ||
+                                std::is_same<CharT, const char>::value ||
+                                !std::is_const<CharT>::value>::type>
+  MOZ_ALWAYS_INLINE JSAtom* lookup(CharT* chars, size_t length) {
+    // Collapse calls for |char*| or |const char*| into |const unsigned char*|
+    // to avoid excess instantiations.  Collapse the remaining |CharT*| to
+    // |const CharT*| for the same reason.
+    using UnsignedCharT = typename std::make_unsigned<CharT>::type;
+    UnsignedCharT* unsignedChars = reinterpret_cast<UnsignedCharT*>(chars);
+    return lookup(const_cast<const UnsignedCharT*>(unsignedChars), length);
+  }
+
  private:
   typedef uint8_t SmallChar;
   static const SmallChar INVALID_SMALL_CHAR = -1;
 
   static bool fitsInSmallChar(char16_t c) {
     return c < SMALL_CHAR_LIMIT && toSmallChar[c] != INVALID_SMALL_CHAR;
   }
 
@@ -1489,26 +1529,33 @@ static inline UniqueChars StringToNewUTF
 
   return UniqueChars(
       linear->hasLatin1Chars()
           ? JS::CharsToNewUTF8CharsZ(maybecx, linear->latin1Range(nogc)).c_str()
           : JS::CharsToNewUTF8CharsZ(maybecx, linear->twoByteRange(nogc))
                 .c_str());
 }
 
-/* GC-allocate a string descriptor for the given malloc-allocated chars. */
+/**
+ * Allocate a string with the given contents, potentially GCing in the process.
+ */
 template <typename CharT>
-extern JSFlatString* NewString(JSContext* cx, CharT* chars, size_t length);
+extern JSFlatString* NewString(JSContext* cx,
+                               UniquePtr<CharT[], JS::FreePolicy> chars,
+                               size_t length);
 
-/* Like NewString, but doesn't try to deflate to Latin1. */
+/* Like NewString, but doesn't attempt to deflate to Latin1. */
 template <typename CharT>
-extern JSFlatString* NewStringDontDeflate(JSContext* cx, CharT* chars,
-                                          size_t length);
+extern JSFlatString* NewStringDontDeflate(
+    JSContext* cx, UniquePtr<CharT[], JS::FreePolicy> chars, size_t length);
 
-/* GC-allocate a string descriptor for the given malloc-allocated chars. */
+/**
+ * Allocate a string with the given contents.  If |allowGC == CanGC|, this may
+ * trigger a GC.
+ */
 template <js::AllowGC allowGC, typename CharT>
 extern JSFlatString* NewString(JSContext* cx,
                                UniquePtr<CharT[], JS::FreePolicy> chars,
                                size_t length);
 
 /* Like NewString, but doesn't try to deflate to Latin1. */
 template <js::AllowGC allowGC, typename CharT>
 extern JSFlatString* NewStringDontDeflate(
@@ -1555,16 +1602,23 @@ inline JSFlatString* NewStringCopyUTF8Z(
   return NewStringCopyUTF8N<allowGC>(
       cx, JS::UTF8Chars(utf8.c_str(), strlen(utf8.c_str())));
 }
 
 JSString* NewMaybeExternalString(JSContext* cx, const char16_t* s, size_t n,
                                  const JSStringFinalizer* fin,
                                  bool* allocatedExternal);
 
+/**
+ * Allocate a new string consisting of |chars[0..length]| characters.
+ */
+extern JSFlatString* NewStringFromLittleEndianNoGC(JSContext* cx,
+                                                   LittleEndianChars chars,
+                                                   size_t length);
+
 JS_STATIC_ASSERT(sizeof(HashNumber) == 4);
 
 template <AllowGC allowGC>
 extern JSString* ConcatStrings(
     JSContext* cx, typename MaybeRooted<JSString*, allowGC>::HandleType left,
     typename MaybeRooted<JSString*, allowGC>::HandleType right);
 
 /*
--- a/js/src/vm/Xdr.cpp
+++ b/js/src/vm/Xdr.cpp
@@ -2,17 +2,16 @@
  * 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 "vm/Xdr.h"
 
 #include "mozilla/ArrayUtils.h"
-#include "mozilla/PodOperations.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Utf8.h"
 
 #include <algorithm>  // std::transform
 #include <string.h>
 #include <type_traits>  // std::is_same
 #include <utility>      // std::move
 
@@ -82,31 +81,32 @@ XDRResult XDRState<mode>::codeChars(Utf8
 }
 
 template <XDRMode mode>
 XDRResult XDRState<mode>::codeChars(char16_t* chars, size_t nchars) {
   if (nchars == 0) {
     return Ok();
   }
 
-  // Align the buffer to avoid unaligned loads.
-  MOZ_TRY(codeAlign(sizeof(char16_t)));
-
   size_t nbytes = nchars * sizeof(char16_t);
   if (mode == XDR_ENCODE) {
     uint8_t* ptr = buf.write(nbytes);
     if (!ptr) {
       return fail(JS::TranscodeResult_Throw);
     }
+
+    // |mozilla::NativeEndian| correctly handles writing into unaligned |ptr|.
     mozilla::NativeEndian::copyAndSwapToLittleEndian(ptr, chars, nchars);
   } else {
     const uint8_t* ptr = buf.read(nbytes);
     if (!ptr) {
       return fail(JS::TranscodeResult_Failure_BadDecode);
     }
+
+    // |mozilla::NativeEndian| correctly handles reading from unaligned |ptr|.
     mozilla::NativeEndian::copyAndSwapFromLittleEndian(chars, ptr, nchars);
   }
   return Ok();
 }
 
 template <XDRMode mode>
 static XDRResult VersionCheck(XDRState<mode>* xdr) {
   JS::BuildIdCharVector buildId;
@@ -191,53 +191,42 @@ XDRResult XDRState<mode>::codeScript(Mut
   AutoTraceLog tl(logger, event);
 
 #ifdef DEBUG
   auto sanityCheck = mozilla::MakeScopeExit(
       [&] { MOZ_ASSERT(validateResultCode(cx(), resultCode())); });
 #endif
   auto guard = mozilla::MakeScopeExit([&] { scriptp.set(nullptr); });
 
-  // This should be a no-op when encoding, but when decoding it would eat any
-  // miss-aligned bytes if when encoding the XDR buffer is appended at the end
-  // of an existing buffer, such as in XDRIncrementalEncoder::linearize
-  // function.
-  MOZ_TRY(codeAlign(sizeof(js::XDRAlignment)));
   AutoXDRTree scriptTree(this, getTopLevelTreeKey());
 
   if (mode == XDR_DECODE) {
     scriptp.set(nullptr);
   } else {
     MOZ_ASSERT(!scriptp->enclosingScope());
   }
 
   MOZ_TRY(VersionCheck(this));
   MOZ_TRY(XDRScript(this, nullptr, nullptr, nullptr, scriptp));
-  MOZ_TRY(codeAlign(sizeof(js::XDRAlignment)));
 
   guard.release();
   return Ok();
 }
 
 template class js::XDRState<XDR_ENCODE>;
 template class js::XDRState<XDR_DECODE>;
 
 AutoXDRTree::AutoXDRTree(XDRCoderBase* xdr, AutoXDRTree::Key key)
     : key_(key), parent_(this), xdr_(xdr) {
-  // Expect sub-tree to start with the maximum alignment required.
-  MOZ_ASSERT(xdr->isAligned(sizeof(js::XDRAlignment)));
   if (key_ != AutoXDRTree::noKey) {
     xdr->createOrReplaceSubTree(this);
   }
 }
 
 AutoXDRTree::~AutoXDRTree() {
-  // Expect sub-tree to end with the maximum alignment required.
-  MOZ_ASSERT_IF(xdr_->resultCode() == JS::TranscodeResult_Ok,
-                xdr_->isAligned(sizeof(js::XDRAlignment)));
   if (key_ != AutoXDRTree::noKey) {
     xdr_->endSubTree();
   }
 }
 
 constexpr AutoXDRTree::Key AutoXDRTree::noKey;
 constexpr AutoXDRTree::Key AutoXDRTree::noSubTree;
 constexpr AutoXDRTree::Key AutoXDRTree::topLevel;
@@ -402,28 +391,16 @@ XDRResult XDRIncrementalEncoder::lineari
   if (oom_) {
     ReportOutOfMemory(cx());
     return fail(JS::TranscodeResult_Throw);
   }
 
   // Do not linearize while we are currently adding bytes.
   MOZ_ASSERT(scope_ == nullptr);
 
-  // Ensure the content of the buffer is properly aligned within the buffer.
-  // This alignment difference should be consumed by the codeAlign function
-  // call of codeScript when decoded.
-  size_t alignLen = sizeof(js::XDRAlignment);
-  if (buffer.length() % alignLen) {
-    alignLen = ComputeByteAlignment(buffer.length(), alignLen);
-    if (!buffer.appendN(0, alignLen)) {
-      ReportOutOfMemory(cx());
-      return fail(JS::TranscodeResult_Throw);
-    }
-  }
-
   // Visit the tree parts in a depth first order to linearize the bits.
   // Calculate the total length first so we don't incur repeated copying
   // and zeroing of memory for large trees.
   DepthFirstSliceIterator dfs(cx(), tree_);
 
   size_t totalLength = buffer.length();
   auto sliceCounter = [&](const Slice& slice) -> bool {
     totalLength += slice.sliceLength;
@@ -440,18 +417,16 @@ XDRResult XDRIncrementalEncoder::lineari
     return fail(JS::TranscodeResult_Throw);
   }
 
   auto sliceCopier = [&](const Slice& slice) -> bool {
     // Copy the bytes associated with the current slice to the transcode
     // buffer which would be serialized.
     MOZ_ASSERT(slice.sliceBegin <= slices_.length());
     MOZ_ASSERT(slice.sliceBegin + slice.sliceLength <= slices_.length());
-    MOZ_ASSERT(buffer.length() % sizeof(XDRAlignment) == 0);
-    MOZ_ASSERT(slice.sliceLength % sizeof(XDRAlignment) == 0);
 
     buffer.infallibleAppend(slices_.begin() + slice.sliceBegin,
                             slice.sliceLength);
     return true;
   };
 
   if (!dfs.iterate(sliceCopier)) {
     ReportOutOfMemory(cx());
--- a/js/src/vm/Xdr.h
+++ b/js/src/vm/Xdr.h
@@ -41,28 +41,16 @@ class XDRBufferBase {
 #endif
   {
   }
 
   JSContext* cx() const { return context_; }
 
   size_t cursor() const { return cursor_; }
 
-#ifdef DEBUG
-  // This function records if the cursor got changed by codeAlign or by any
-  // other read/write of data. This is used for AutoXDRTree assertions, as a
-  // way to ensure that the last thing done is properly setting the alignment
-  // with codeAlign function.
-  void setAligned(bool aligned) { aligned_ = aligned; }
-  bool isAligned() const { return aligned_; }
-#else
-  void setAligned(bool) const {}
-  bool isAligned() const { return true; }
-#endif
-
  protected:
   JSContext* const context_;
   size_t cursor_;
 #ifdef DEBUG
   bool aligned_;
 #endif
 };
 
@@ -72,17 +60,16 @@ class XDRBuffer;
 template <>
 class XDRBuffer<XDR_ENCODE> : public XDRBufferBase {
  public:
   XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
       : XDRBufferBase(cx, cursor), buffer_(buffer) {}
 
   uint8_t* write(size_t n) {
     MOZ_ASSERT(n != 0);
-    setAligned(false);
     if (!buffer_.growByUninitialized(n)) {
       ReportOutOfMemory(cx());
       return nullptr;
     }
     uint8_t* ptr = &buffer_[cursor_];
     cursor_ += n;
     return ptr;
   }
@@ -107,17 +94,16 @@ class XDRBuffer<XDR_DECODE> : public XDR
   XDRBuffer(JSContext* cx, const JS::TranscodeRange& range)
       : XDRBufferBase(cx), buffer_(range) {}
 
   XDRBuffer(JSContext* cx, JS::TranscodeBuffer& buffer, size_t cursor = 0)
       : XDRBufferBase(cx, cursor), buffer_(buffer.begin(), buffer.length()) {}
 
   const uint8_t* read(size_t n) {
     MOZ_ASSERT(cursor_ < buffer_.length());
-    setAligned(false);
     uint8_t* ptr = &buffer_[cursor_];
     cursor_ += n;
 
     // Don't let buggy code read past our buffer
     if (cursor_ > buffer_.length()) {
       return nullptr;
     }
 
@@ -135,18 +121,16 @@ class XDRBuffer<XDR_DECODE> : public XDR
   }
 
  private:
   const JS::TranscodeRange buffer_;
 };
 
 class XDRCoderBase;
 class XDRIncrementalEncoder;
-using XDRAlignment = char16_t;
-static const uint8_t AlignPadding[sizeof(XDRAlignment)] = {0, 0};
 
 // An AutoXDRTree is used to identify section encoded by an
 // XDRIncrementalEncoder.
 //
 // Its primary goal is to identify functions, such that we can first encode them
 // as LazyScript, and later replaced by them by their corresponding bytecode
 // once delazified.
 //
@@ -202,17 +186,16 @@ class XDRCoderBase {
   virtual AutoXDRTree::Key getTopLevelTreeKey() const {
     return AutoXDRTree::noKey;
   }
   virtual AutoXDRTree::Key getTreeKey(JSFunction* fun) const {
     return AutoXDRTree::noKey;
   }
   virtual void createOrReplaceSubTree(AutoXDRTree* child){};
   virtual void endSubTree(){};
-  virtual bool isAligned(size_t n) = 0;
 
 #ifdef DEBUG
   // Record logical failures of XDR.
   JS::TranscodeResult resultCode() const { return resultCode_; }
   void setResultCode(JS::TranscodeResult code) {
     MOZ_ASSERT(resultCode() == JS::TranscodeResult_Ok);
     resultCode_ = code;
   }
@@ -261,56 +244,16 @@ class XDRState : public XDRCoderBase {
     const uint8_t* ptr = buf.read(length);
     if (!ptr) {
       return fail(JS::TranscodeResult_Failure_BadDecode);
     }
     *pptr = ptr;
     return Ok();
   }
 
-  // Alignment is required when doing memcpy of data which contains element
-  // largers than 1 byte.
-  bool isAligned(size_t n) override {
-    MOZ_ASSERT(mozilla::IsPowerOfTwo(n));
-    size_t mask = n - 1;
-    size_t offset = buf.uptr() & mask;
-    // In debug build, we not only check if the cursor is aligned, but also
-    // if the last cursor manipulation was made by the codeAlign function.
-    return offset == 0 && buf.isAligned();
-  }
-  XDRResult codeAlign(size_t n) {
-    MOZ_ASSERT(mozilla::IsPowerOfTwo(n));
-    size_t mask = n - 1;
-    MOZ_ASSERT_IF(mode == XDR_ENCODE,
-                  (buf.uptr() & mask) == (buf.cursor() & mask));
-    size_t offset = buf.uptr() & mask;
-    if (offset) {
-      size_t padding = n - offset;
-      MOZ_ASSERT(padding < sizeof(AlignPadding));
-      if (mode == XDR_ENCODE) {
-        uint8_t* ptr = buf.write(padding);
-        if (!ptr) {
-          return fail(JS::TranscodeResult_Throw);
-        }
-        memcpy(ptr, AlignPadding, padding);
-      } else {
-        const uint8_t* ptr = buf.read(padding);
-        if (!ptr) {
-          return fail(JS::TranscodeResult_Failure_BadDecode);
-        }
-        if (memcmp(ptr, AlignPadding, padding) != 0) {
-          return fail(JS::TranscodeResult_Failure_BadDecode);
-        }
-      }
-    }
-    buf.setAligned(true);
-    MOZ_ASSERT(isAligned(n));
-    return Ok();
-  }
-
   XDRResult codeUint8(uint8_t* n) {
     if (mode == XDR_ENCODE) {
       uint8_t* ptr = buf.write(sizeof(*n));
       if (!ptr) {
         return fail(JS::TranscodeResult_Throw);
       }
       *ptr = *n;
     } else {
@@ -473,18 +416,16 @@ class XDRState : public XDRCoderBase {
       *sp = reinterpret_cast<const char*>(ptr);
     }
     return Ok();
   }
 
   XDRResult codeChars(JS::Latin1Char* chars, size_t nchars);
   XDRResult codeChars(mozilla::Utf8Unit* units, size_t nchars);
 
-  // If |nchars > 0|, this calls |codeAlign(sizeof(char16_t))| so callers
-  // don't have to.
   XDRResult codeChars(char16_t* chars, size_t nchars);
 
   XDRResult codeFunction(JS::MutableHandleFunction objp,
                          HandleScriptSourceObject sourceObject = nullptr);
   XDRResult codeScript(MutableHandleScript scriptp);
 };
 
 using XDREncoder = XDRState<XDR_ENCODE>;
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -55,25 +55,16 @@ static LazyLogModule gLog("ScriptPreload
 
 using mozilla::dom::AutoJSAPI;
 using mozilla::dom::ContentChild;
 using mozilla::dom::ContentParent;
 using namespace mozilla::loader;
 
 ProcessType ScriptPreloader::sProcessType;
 
-// This type correspond to js::vm::XDRAlignment type, which is used as a size
-// reference for alignment of XDR buffers.
-using XDRAlign = uint16_t;
-static const uint8_t sAlignPadding[sizeof(XDRAlign)] = {0, 0};
-
-static inline size_t ComputeByteAlignment(size_t bytes, size_t align) {
-  return (align - (bytes % align)) % align;
-}
-
 nsresult ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
                                          nsISupports* aData, bool aAnonymize) {
   MOZ_COLLECT_REPORT(
       "explicit/script-preloader/heap/saved-scripts", KIND_HEAP, UNITS_BYTES,
       SizeOfHashEntries<ScriptStatus::Saved>(mScripts, MallocSizeOf),
       "Memory used to hold the scripts which have been executed in this "
       "session, and will be written to the startup script cache file.");
 
@@ -535,18 +526,16 @@ Result<Ok, nsresult> ScriptPreloader::In
   auto size = mCacheData.size();
 
   uint32_t headerSize;
   if (size < sizeof(MAGIC) + sizeof(headerSize)) {
     return Err(NS_ERROR_UNEXPECTED);
   }
 
   auto data = mCacheData.get<uint8_t>();
-  uint8_t* start = data.get();
-  MOZ_ASSERT(reinterpret_cast<uintptr_t>(start) % sizeof(XDRAlign) == 0);
   auto end = data + size;
 
   if (memcmp(MAGIC, data.get(), sizeof(MAGIC))) {
     return Err(NS_ERROR_UNEXPECTED);
   }
   data += sizeof(MAGIC);
 
   headerSize = LittleEndian::readUint32(data.get());
@@ -561,20 +550,16 @@ Result<Ok, nsresult> ScriptPreloader::In
 
     LinkedList<CachedScript> scripts;
 
     Range<uint8_t> header(data, data + headerSize);
     data += headerSize;
 
     InputBuffer buf(header);
 
-    size_t len = data.get() - start;
-    size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign));
-    data += alignLen;
-
     size_t offset = 0;
     while (!buf.finished()) {
       auto script = MakeUnique<CachedScript>(*this, buf);
       MOZ_RELEASE_ASSERT(script);
 
       auto scriptData = data + script->mOffset;
       if (scriptData + script->mSize > end) {
         return Err(NS_ERROR_UNEXPECTED);
@@ -582,19 +567,16 @@ Result<Ok, nsresult> ScriptPreloader::In
 
       // Make sure offsets match what we'd expect based on script ordering and
       // size, as a basic sanity check.
       if (script->mOffset != offset) {
         return Err(NS_ERROR_UNEXPECTED);
       }
       offset += script->mSize;
 
-      MOZ_ASSERT(reinterpret_cast<uintptr_t>(scriptData.get()) %
-                     sizeof(XDRAlign) ==
-                 0);
       script->mXDRRange.emplace(scriptData, scriptData + script->mSize);
 
       // Don't pre-decode the script unless it was used in this process type
       // during the previous session.
       if (script->mOriginalProcessTypes.contains(CurrentProcessType())) {
         scripts.insertBack(script.get());
       } else {
         script->mReadyToExecute = true;
@@ -734,42 +716,30 @@ Result<Ok, nsresult> ScriptPreloader::Wr
     // Sort scripts by load time, with async loaded scripts before sync scripts.
     // Since async scripts are always loaded immediately at startup, it helps to
     // have them stored contiguously.
     scripts.Sort(CachedScript::Comparator());
 
     OutputBuffer buf;
     size_t offset = 0;
     for (auto script : scripts) {
-      MOZ_ASSERT(offset % sizeof(XDRAlign) == 0);
       script->mOffset = offset;
       script->Code(buf);
 
       offset += script->mSize;
     }
 
     uint8_t headerSize[4];
     LittleEndian::writeUint32(headerSize, buf.cursor());
 
-    size_t len = 0;
     MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
-    len += sizeof(MAGIC);
     MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
-    len += sizeof(headerSize);
     MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
-    len += buf.cursor();
-    size_t alignLen = ComputeByteAlignment(len, sizeof(XDRAlign));
-    if (alignLen) {
-      MOZ_TRY(Write(fd, sAlignPadding, alignLen));
-      len += alignLen;
-    }
     for (auto script : scripts) {
-      MOZ_ASSERT(script->mSize % sizeof(XDRAlign) == 0);
       MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
-      len += script->mSize;
 
       if (script->mScript) {
         script->FreeData();
       }
     }
   }
 
   MOZ_TRY(cacheFile->MoveTo(nullptr, mBaseName + NS_LITERAL_STRING(".bin")));
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -276,34 +276,35 @@ bool XPCConvert::NativeData2JS(MutableHa
         return false;
       }
 
       // Usage of UTF-8 in XPConnect is mostly for things that are
       // almost always ASCII, so the inexact allocations below
       // should be fine.
 
       if (IsUTF8Latin1(*utf8String)) {
-        char* buffer = static_cast<char*>(JS_malloc(cx, allocLen.value()));
+        using UniqueLatin1Chars =
+            js::UniquePtr<JS::Latin1Char[], JS::FreePolicy>;
+
+        UniqueLatin1Chars buffer(
+            static_cast<JS::Latin1Char*>(JS_malloc(cx, allocLen.value())));
         if (!buffer) {
           return false;
         }
-        size_t written =
-            LossyConvertUTF8toLatin1(*utf8String, MakeSpan(buffer, len));
+
+        size_t written = LossyConvertUTF8toLatin1(
+            *utf8String, MakeSpan(reinterpret_cast<char*>(buffer.get()), len));
         buffer[written] = 0;
 
-        // JS_NewLatin1String takes ownership on success, i.e. a
-        // successful call will make it the responsiblity of the JS VM
-        // to free the buffer.
         // written can never exceed len, so the truncation is OK.
-        JSString* str = JS_NewLatin1String(
-            cx, reinterpret_cast<JS::Latin1Char*>(buffer), written);
+        JSString* str = JS_NewLatin1String(cx, std::move(buffer), written);
         if (!str) {
-          JS_free(cx, buffer);
           return false;
         }
+
         d.setString(str);
         return true;
       }
 
       // 1-byte sequences decode to 1 UTF-16 code unit
       // 2-byte sequences decode to 1 UTF-16 code unit
       // 3-byte sequences decode to 1 UTF-16 code unit
       // 4-byte sequences decode to 2 UTF-16 code units
@@ -311,41 +312,38 @@ bool XPCConvert::NativeData2JS(MutableHa
       // the number of input code units (but see the comment
       // below). allocLen already takes the zero terminator
       // into account.
       allocLen *= sizeof(char16_t);
       if (!allocLen.isValid()) {
         return false;
       }
 
-      char16_t* buffer =
-          static_cast<char16_t*>(JS_malloc(cx, allocLen.value()));
+      JS::UniqueTwoByteChars buffer(
+          static_cast<char16_t*>(JS_malloc(cx, allocLen.value())));
       if (!buffer) {
         return false;
       }
 
       // For its internal simplicity, ConvertUTF8toUTF16 requires the
       // destination to be one code unit longer than the source, but
       // it never actually writes more code units than the number of
       // code units in the source. That's why it's OK to claim the
       // output buffer has len + 1 space but then still expect to
       // have space for the zero terminator.
-      size_t written =
-          ConvertUTF8toUTF16(*utf8String, MakeSpan(buffer, allocLen.value()));
+      size_t written = ConvertUTF8toUTF16(
+          *utf8String, MakeSpan(buffer.get(), allocLen.value()));
       MOZ_RELEASE_ASSERT(written <= len);
       buffer[written] = 0;
 
-      // JS_NewUCStringDontDeflate takes ownership on success, i.e. a
-      // successful call will make it the responsiblity of the JS VM
-      // to free the buffer.
-      JSString* str = JS_NewUCStringDontDeflate(cx, buffer, written);
+      JSString* str = JS_NewUCStringDontDeflate(cx, std::move(buffer), written);
       if (!str) {
-        JS_free(cx, buffer);
         return false;
       }
+
       d.setString(str);
       return true;
     }
     case nsXPTType::T_CSTRING: {
       const nsACString* cString = static_cast<const nsACString*>(s);
 
       if (!cString || cString->IsVoid()) {
         d.setNull();
--- a/mfbt/EndianUtils.h
+++ b/mfbt/EndianUtils.h
@@ -399,17 +399,19 @@ class Endian : private EndianUtils {
    */
   template <typename T>
   MOZ_MUST_USE static T swapToLittleEndian(T aValue) {
     return maybeSwap<ThisEndian, Little>(aValue);
   }
 
   /*
    * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting
-   * them to little-endian format if ThisEndian is Big.
+   * them to little-endian format if ThisEndian is Big.  |aSrc| as a typed
+   * pointer must be aligned; |aDest| need not be.
+   *
    * As with memcpy, |aDest| and |aSrc| must not overlap.
    */
   template <typename T>
   static void copyAndSwapToLittleEndian(void* aDest, const T* aSrc,
                                         size_t aCount) {
     copyAndSwapTo<ThisEndian, Little>(aDest, aSrc, aCount);
   }
 
@@ -426,17 +428,19 @@ class Endian : private EndianUtils {
    */
   template <typename T>
   MOZ_MUST_USE static T swapToBigEndian(T aValue) {
     return maybeSwap<ThisEndian, Big>(aValue);
   }
 
   /*
    * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting
-   * them to big-endian format if ThisEndian is Little.
+   * them to big-endian format if ThisEndian is Little.  |aSrc| as a typed
+   * pointer must be aligned; |aDest| need not be.
+   *
    * As with memcpy, |aDest| and |aSrc| must not overlap.
    */
   template <typename T>
   static void copyAndSwapToBigEndian(void* aDest, const T* aSrc,
                                      size_t aCount) {
     copyAndSwapTo<ThisEndian, Big>(aDest, aSrc, aCount);
   }
 
@@ -474,17 +478,19 @@ class Endian : private EndianUtils {
    */
   template <typename T>
   MOZ_MUST_USE static T swapFromLittleEndian(T aValue) {
     return maybeSwap<Little, ThisEndian>(aValue);
   }
 
   /*
    * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting
-   * them to little-endian format if ThisEndian is Big.
+   * them to little-endian format if ThisEndian is Big.  |aDest| as a typed
+   * pointer must be aligned; |aSrc| need not be.
+   *
    * As with memcpy, |aDest| and |aSrc| must not overlap.
    */
   template <typename T>
   static void copyAndSwapFromLittleEndian(T* aDest, const void* aSrc,
                                           size_t aCount) {
     copyAndSwapFrom<Little, ThisEndian>(aDest, aSrc, aCount);
   }
 
@@ -501,17 +507,19 @@ class Endian : private EndianUtils {
    */
   template <typename T>
   MOZ_MUST_USE static T swapFromBigEndian(T aValue) {
     return maybeSwap<Big, ThisEndian>(aValue);
   }
 
   /*
    * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting
-   * them to big-endian format if ThisEndian is Little.
+   * them to big-endian format if ThisEndian is Little.  |aDest| as a typed
+   * pointer must be aligned; |aSrc| need not be.
+   *
    * As with memcpy, |aDest| and |aSrc| must not overlap.
    */
   template <typename T>
   static void copyAndSwapFromBigEndian(T* aDest, const void* aSrc,
                                        size_t aCount) {
     copyAndSwapFrom<Big, ThisEndian>(aDest, aSrc, aCount);
   }
 
--- a/mfbt/HashFunctions.h
+++ b/mfbt/HashFunctions.h
@@ -50,16 +50,17 @@
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Char16.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Types.h"
 #include "mozilla/WrappingOperations.h"
 
 #include <stdint.h>
+#include <type_traits>
 
 namespace mozilla {
 
 using HashNumber = uint32_t;
 static const uint32_t kHashNumberBits = 32;
 
 /**
  * The golden ratio as a 32-bit fixed-point value.
@@ -221,83 +222,102 @@ MOZ_MUST_USE HashNumber AddToHash(HashNu
  * much better than calling AddToHash(x, y), because AddToHash(x, y) assumes
  * that x has already been hashed.
  */
 template <typename... Args>
 MOZ_MUST_USE inline HashNumber HashGeneric(Args... aArgs) {
   return AddToHash(0, aArgs...);
 }
 
-namespace detail {
-
-template <typename T>
-constexpr HashNumber HashUntilZero(const T* aStr) {
+/**
+ * Hash successive |*aIter| until |!*aIter|, i.e. til null-termination.
+ *
+ * This function is *not* named HashString like the non-template overloads
+ * below.  Some users define HashString overloads and pass inexactly-matching
+ * values to them -- but an inexactly-matching value would match this overload
+ * instead!  We follow the general rule and don't mix and match template and
+ * regular overloads to avoid this.
+ *
+ * If you have the string's length, call HashStringKnownLength: it may be
+ * marginally faster.
+ */
+template <typename Iterator>
+MOZ_MUST_USE constexpr HashNumber HashStringUntilZero(Iterator aIter) {
   HashNumber hash = 0;
-  for (; T c = *aStr; aStr++) {
+  for (; auto c = *aIter; ++aIter) {
     hash = AddToHash(hash, c);
   }
   return hash;
 }
 
-template <typename T>
-HashNumber HashKnownLength(const T* aStr, size_t aLength) {
+/**
+ * Hash successive |aIter[i]| up to |i == aLength|.
+ */
+template <typename Iterator>
+MOZ_MUST_USE constexpr HashNumber HashStringKnownLength(Iterator aIter,
+                                                        size_t aLength) {
   HashNumber hash = 0;
   for (size_t i = 0; i < aLength; i++) {
-    hash = AddToHash(hash, aStr[i]);
+    hash = AddToHash(hash, aIter[i]);
   }
   return hash;
 }
 
-} /* namespace detail */
-
 /**
  * The HashString overloads below do just what you'd expect.
  *
- * If you have the string's length, you might as well call the overload which
- * includes the length.  It may be marginally faster.
+ * These functions are non-template functions so that users can 1) overload them
+ * with their own types 2) in a way that allows implicit conversions to happen.
  */
 MOZ_MUST_USE inline HashNumber HashString(const char* aStr) {
-  return detail::HashUntilZero(reinterpret_cast<const unsigned char*>(aStr));
+  // Use the |const unsigned char*| version of the above so that all ordinary
+  // character data hashes identically.
+  return HashStringUntilZero(reinterpret_cast<const unsigned char*>(aStr));
 }
 
 MOZ_MUST_USE inline HashNumber HashString(const char* aStr, size_t aLength) {
-  return detail::HashKnownLength(reinterpret_cast<const unsigned char*>(aStr),
-                                 aLength);
+  // Delegate to the |const unsigned char*| version of the above to share
+  // template instantiations.
+  return HashStringKnownLength(reinterpret_cast<const unsigned char*>(aStr),
+                               aLength);
 }
 
 MOZ_MUST_USE
 inline HashNumber HashString(const unsigned char* aStr, size_t aLength) {
-  return detail::HashKnownLength(aStr, aLength);
+  return HashStringKnownLength(aStr, aLength);
 }
 
 // You may need to use the
 // MOZ_{PUSH,POP}_DISABLE_INTEGRAL_CONSTANT_OVERFLOW_WARNING macros if you use
 // this function. See the comment on those macros' definitions for more detail.
 MOZ_MUST_USE constexpr HashNumber HashString(const char16_t* aStr) {
-  return detail::HashUntilZero(aStr);
+  return HashStringUntilZero(aStr);
 }
 
 MOZ_MUST_USE inline HashNumber HashString(const char16_t* aStr,
                                           size_t aLength) {
-  return detail::HashKnownLength(aStr, aLength);
+  return HashStringKnownLength(aStr, aLength);
 }
 
-/*
- * On Windows, wchar_t is not the same as char16_t, even though it's
- * the same width!
+/**
+ * HashString overloads for |wchar_t| on platforms where it isn't |char16_t|.
  */
-#ifdef WIN32
-MOZ_MUST_USE inline HashNumber HashString(const wchar_t* aStr) {
-  return detail::HashUntilZero(aStr);
+template <typename WCharT, typename = typename std::enable_if<
+                               std::is_same<WCharT, wchar_t>::value &&
+                               !std::is_same<wchar_t, char16_t>::value>::type>
+MOZ_MUST_USE inline HashNumber HashString(const WCharT* aStr) {
+  return HashStringUntilZero(aStr);
 }
 
-MOZ_MUST_USE inline HashNumber HashString(const wchar_t* aStr, size_t aLength) {
-  return detail::HashKnownLength(aStr, aLength);
+template <typename WCharT, typename = typename std::enable_if<
+                               std::is_same<WCharT, wchar_t>::value &&
+                               !std::is_same<wchar_t, char16_t>::value>::type>
+MOZ_MUST_USE inline HashNumber HashString(const WCharT* aStr, size_t aLength) {
+  return HashStringKnownLength(aStr, aLength);
 }
-#endif
 
 /**
  * Hash some number of bytes.
  *
  * This hash walks word-by-word, rather than byte-by-byte, so you won't get the
  * same result out of HashBytes as you would out of HashString.
  */
 MOZ_MUST_USE extern MFBT_API HashNumber HashBytes(const void* bytes,