Bug 1499170 - Add an atom bit to know whether we're ascii lowercase. r=njn
authorEmilio Cobos Álvarez <emilio@crisal.io>
Tue, 13 Nov 2018 12:47:40 +0000
changeset 502451 2655a3d8dcd9f1a27f5b1cd8f5cbbccd42d4cc52
parent 502450 de9cc5033d87effbb7817e5179540c64d0cb97a0
child 502459 84b9cfcef11a337689bfb15944cf480e3cec415a
child 502473 80b0b8b446fdb9887025e2d5bb29535e11167878
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1499170
milestone65.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 1499170 - Add an atom bit to know whether we're ascii lowercase. r=njn And thus massively speed up ascii-case-insensitive atom comparisons when both atoms are lowercase (which is the common case by far). This removes almost all the slow selector-matching in this page, and it seems an easier fix than storing the lowercased version of all class-names in quirks mode in elements and selectors... Differential Revision: https://phabricator.services.mozilla.com/D10945
dom/base/nsAttrValue.cpp
dom/base/nsContentUtils.h
servo/components/style/gecko/regen_atoms.py
servo/components/style/gecko_string_cache/mod.rs
xpcom/ds/Atom.py
xpcom/ds/StaticAtoms.py
xpcom/ds/nsAtom.h
xpcom/ds/nsAtomTable.cpp
xpcom/ds/nsGkAtoms.cpp
xpcom/ds/nsGkAtoms.h
--- a/dom/base/nsAttrValue.cpp
+++ b/dom/base/nsAttrValue.cpp
@@ -1164,45 +1164,37 @@ nsAttrValue::EqualsAsStrings(const nsAtt
 
 bool
 nsAttrValue::Contains(nsAtom* aValue, nsCaseTreatment aCaseSensitive) const
 {
   switch (BaseType()) {
     case eAtomBase:
     {
       nsAtom* atom = GetAtomValue();
-
       if (aCaseSensitive == eCaseMatters) {
         return aValue == atom;
       }
 
       // For performance reasons, don't do a full on unicode case insensitive
       // string comparison. This is only used for quirks mode anyway.
-      return
-        nsContentUtils::EqualsIgnoreASCIICase(nsDependentAtomString(aValue),
-                                              nsDependentAtomString(atom));
+      return nsContentUtils::EqualsIgnoreASCIICase(aValue, atom);
     }
     default:
     {
       if (Type() == eAtomArray) {
         AtomArray* array = GetAtomArrayValue();
         if (aCaseSensitive == eCaseMatters) {
           return array->Contains(aValue);
         }
 
-        nsDependentAtomString val1(aValue);
-
-        for (RefPtr<nsAtom> *cur = array->Elements(),
-                               *end = cur + array->Length();
-             cur != end; ++cur) {
+        for (RefPtr<nsAtom>& cur : *array) {
           // For performance reasons, don't do a full on unicode case
           // insensitive string comparison. This is only used for quirks mode
           // anyway.
-          if (nsContentUtils::EqualsIgnoreASCIICase(val1,
-                nsDependentAtomString(*cur))) {
+          if (nsContentUtils::EqualsIgnoreASCIICase(aValue, cur)) {
             return true;
           }
         }
       }
     }
   }
 
   return false;
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2054,16 +2054,35 @@ public:
    * See Bug #436083
    */
   static nsresult ProcessViewportInfo(nsIDocument *aDocument,
                                       const nsAString &viewportInfo);
 
   static JSContext *GetCurrentJSContext();
 
   /**
+   * Case insensitive comparison between two atoms.
+   */
+  static bool EqualsIgnoreASCIICase(nsAtom* aAtom1, nsAtom* aAtom2)
+  {
+    if (aAtom1 == aAtom2) {
+      return true;
+    }
+
+    // If both are ascii lowercase already, we know that the slow comparison
+    // below is going to return false.
+    if (aAtom1->IsAsciiLowercase() && aAtom2->IsAsciiLowercase()) {
+      return false;
+    }
+
+    return EqualsIgnoreASCIICase(nsDependentAtomString(aAtom1),
+                                 nsDependentAtomString(aAtom2));
+  }
+
+  /**
    * Case insensitive comparison between two strings. However it only ignores
    * case for ASCII characters a-z.
    */
   static bool EqualsIgnoreASCIICase(const nsAString& aStr1,
                                     const nsAString& aStr2);
 
   /**
    * Convert ASCII A-Z to a-z.
--- a/servo/components/style/gecko/regen_atoms.py
+++ b/servo/components/style/gecko/regen_atoms.py
@@ -11,18 +11,18 @@ import sys
 from io import BytesIO
 
 GECKO_DIR = os.path.dirname(__file__.replace('\\', '/'))
 sys.path.insert(0, os.path.join(os.path.dirname(GECKO_DIR), "properties"))
 
 import build
 
 
-# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, nsStaticAtom, PseudoElementAtom)`.
-PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*([^,]*),\s*([^)]*)\)',
+# Matches lines like `GK_ATOM(foo, "foo", 0x12345678, true, nsStaticAtom, PseudoElementAtom)`.
+PATTERN = re.compile('^GK_ATOM\(([^,]*),[^"]*"([^"]*)",\s*(0x[0-9a-f]+),\s*[^,]*,\s*([^,]*),\s*([^)]*)\)',
                      re.MULTILINE)
 FILE = "include/nsGkAtomList.h"
 
 
 def map_atom(ident):
     if ident in {"box", "loop", "match", "mod", "ref",
                  "self", "type", "use", "where", "in"}:
         return ident + "_"
--- a/servo/components/style/gecko_string_cache/mod.rs
+++ b/servo/components/style/gecko_string_cache/mod.rs
@@ -170,23 +170,29 @@ impl WeakAtom {
         };
 
         cb(utf8_slice)
     }
 
     /// Returns whether this atom is static.
     #[inline]
     pub fn is_static(&self) -> bool {
-        unsafe { (*self.as_ptr()).mIsStatic() != 0 }
+        self.0.mIsStatic() != 0
+    }
+
+    /// Returns whether this atom is ascii lowercase.
+    #[inline]
+    fn is_ascii_lowercase(&self) -> bool {
+        self.0.mIsAsciiLowercase() != 0
     }
 
     /// Returns the length of the atom string.
     #[inline]
     pub fn len(&self) -> u32 {
-        unsafe { (*self.as_ptr()).mLength() }
+        self.0.mLength()
     }
 
     /// Returns whether this atom is the empty string.
     #[inline]
     pub fn is_empty(&self) -> bool {
         self.len() == 0
     }
 
@@ -194,65 +200,71 @@ impl WeakAtom {
     #[inline]
     pub fn as_ptr(&self) -> *mut nsAtom {
         let const_ptr: *const nsAtom = &self.0;
         const_ptr as *mut nsAtom
     }
 
     /// Convert this atom to ASCII lower-case
     pub fn to_ascii_lowercase(&self) -> Atom {
+        if self.is_ascii_lowercase() {
+            return self.clone();
+        }
+
         let slice = self.as_slice();
-        match slice
-            .iter()
-            .position(|&char16| (b'A' as u16) <= char16 && char16 <= (b'Z' as u16))
-        {
-            None => self.clone(),
-            Some(i) => {
-                let mut buffer: [u16; 64] = unsafe { mem::uninitialized() };
-                let mut vec;
-                let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) {
-                    buffer_prefix.copy_from_slice(slice);
-                    buffer_prefix
-                } else {
-                    vec = slice.to_vec();
-                    &mut vec
-                };
-                for char16 in &mut mutable_slice[i..] {
-                    if *char16 <= 0x7F {
-                        *char16 = (*char16 as u8).to_ascii_lowercase() as u16
-                    }
-                }
-                Atom::from(&*mutable_slice)
-            },
+        let mut buffer: [u16; 64] = unsafe { mem::uninitialized() };
+        let mut vec;
+        let mutable_slice = if let Some(buffer_prefix) = buffer.get_mut(..slice.len()) {
+            buffer_prefix.copy_from_slice(slice);
+            buffer_prefix
+        } else {
+            vec = slice.to_vec();
+            &mut vec
+        };
+        for char16 in &mut *mutable_slice {
+            if *char16 <= 0x7F {
+                *char16 = (*char16 as u8).to_ascii_lowercase() as u16
+            }
         }
+        Atom::from(&*mutable_slice)
     }
 
     /// Return whether two atoms are ASCII-case-insensitive matches
+    #[inline]
     pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool {
         if self == other {
             return true;
         }
 
+        // If we know both atoms are ascii-lowercase, then we can stick with
+        // pointer equality.
+        if self.is_ascii_lowercase() && other.is_ascii_lowercase() {
+            debug_assert!(!self.eq_ignore_ascii_case_slow(other));
+            return false;
+        }
+
+        self.eq_ignore_ascii_case_slow(other)
+    }
+
+    fn eq_ignore_ascii_case_slow(&self, other: &Self) -> bool {
         let a = self.as_slice();
         let b = other.as_slice();
-        a.len() == b.len() && a.iter().zip(b).all(|(&a16, &b16)| {
+
+        if a.len() != b.len() {
+            return false;
+        }
+
+        a.iter().zip(b).all(|(&a16, &b16)| {
             if a16 <= 0x7F && b16 <= 0x7F {
                 (a16 as u8).eq_ignore_ascii_case(&(b16 as u8))
             } else {
                 a16 == b16
             }
         })
     }
-
-    /// Return whether this atom is an ASCII-case-insensitive match for the given string
-    pub fn eq_str_ignore_ascii_case(&self, other: &str) -> bool {
-        self.chars()
-            .map(|r| r.map(|c: char| c.to_ascii_lowercase()))
-            .eq(other.chars().map(|c: char| Ok(c.to_ascii_lowercase())))
-    }
 }
 
 impl fmt::Debug for WeakAtom {
     fn fmt(&self, w: &mut fmt::Formatter) -> fmt::Result {
         write!(w, "Gecko WeakAtom({:p}, {})", self, self)
     }
 }
 
--- a/xpcom/ds/Atom.py
+++ b/xpcom/ds/Atom.py
@@ -5,16 +5,17 @@
 
 class Atom():
     def __init__(self, ident, string, ty="nsStaticAtom"):
         self.ident = ident
         self.string = string
         self.ty = ty
         self.atom_type = self.__class__.__name__
         self.hash = hash_string(string)
+        self.is_ascii_lowercase = is_ascii_lowercase(string)
 
 
 class PseudoElementAtom(Atom):
     def __init__(self, ident, string):
         Atom.__init__(self, ident, string, ty="nsCSSPseudoElementStaticAtom")
 
 
 class AnonBoxAtom(Atom):
@@ -47,8 +48,17 @@ def wrapping_multiply(x, y):
 # mozilla::HashString(const char16_t*), which is what we use for atomizing
 # strings. An assertion in nsAtomTable::RegisterStaticAtoms ensures that
 # the value we compute here matches what HashString() would produce.
 def hash_string(s):
     h = 0
     for c in s:
         h = wrapping_multiply(GOLDEN_RATIO_U32, rotate_left_5(h) ^ ord(c))
     return h
+
+
+# Returns true if lowercasing this string in an ascii-case-insensitive way
+# would leave the string unchanged.
+def is_ascii_lowercase(s):
+    for c in s:
+        if c >= 'A' and c <= 'Z':
+            return False
+    return True
--- a/xpcom/ds/StaticAtoms.py
+++ b/xpcom/ds/StaticAtoms.py
@@ -2350,19 +2350,19 @@ def verify():
 
 
 def generate_nsgkatomlist_h(output, *ignore):
     verify()
     output.write("/* THIS FILE IS AUTOGENERATED BY StaticAtoms.py.  DO NOT EDIT */\n\n"
                  "#ifdef small\n"
                  "#undef small\n"
                  "#endif\n\n"
-                 "// GK_ATOM(identifier, string, hash, gecko_type, atom_type)\n" +
-                 "".join(["GK_ATOM(%s, \"%s\", 0x%08x, %s, %s)\n" %
-                            (a.ident, a.string, a.hash, a.ty, a.atom_type)
+                 "// GK_ATOM(identifier, string, hash, is_ascii_lower, gecko_type, atom_type)\n" +
+                 "".join(["GK_ATOM(%s, \"%s\", 0x%08x, %s, %s, %s)\n" %
+                            (a.ident, a.string, a.hash, str(a.is_ascii_lowercase).lower(), a.ty, a.atom_type)
                           for a in STATIC_ATOMS]))
 
 
 def generate_nsgkatomconsts_h(output, *ignore):
     pseudo_index = None
     anon_box_index = None
     pseudo_count = 0
     anon_box_count = 0
--- a/xpcom/ds/nsAtom.h
+++ b/xpcom/ds/nsAtom.h
@@ -16,19 +16,17 @@ struct AtomsSizes;
 }
 
 class nsStaticAtom;
 class nsDynamicAtom;
 
 // This class encompasses both static and dynamic atoms.
 //
 // - In places where static and dynamic atoms can be used, use RefPtr<nsAtom>.
-//   This is by far the most common case. (The exception to this is the HTML5
-//   parser, which does its own weird thing, and uses non-refcounted dynamic
-//   atoms.)
+//   This is by far the most common case.
 //
 // - In places where only static atoms can appear, use nsStaticAtom* to avoid
 //   unnecessary refcounting. This is a moderately common case.
 //
 // - In places where only dynamic atoms can appear, it doesn't matter much
 //   whether you use RefPtr<nsAtom> or RefPtr<nsDynamicAtom>. This is an
 //   extremely rare case.
 //
@@ -70,44 +68,55 @@ public:
 
   // A hashcode that is better distributed than the actual atom pointer, for
   // use in situations that need a well-distributed hashcode. It's called hash()
   // rather than Hash() so we can use mozilla::BloomFilter<N, nsAtom>, because
   // BloomFilter requires elements to implement a function called hash().
   //
   uint32_t hash() const { return mHash; }
 
+  // This function returns true if ToLowercaseASCII would return the string
+  // unchanged.
+  bool IsAsciiLowercase() const
+  {
+    return mIsAsciiLowercase;
+  }
+
   // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting
   // of this type is special.
   MozExternalRefCountType AddRef();
   MozExternalRefCountType Release();
 
   typedef mozilla::TrueType HasThreadSafeRefCnt;
 
 protected:
   // Used by nsStaticAtom.
-  constexpr nsAtom(uint32_t aLength, uint32_t aHash)
+  constexpr nsAtom(uint32_t aLength, uint32_t aHash, bool aIsAsciiLowercase)
     : mLength(aLength)
     , mIsStatic(true)
+    , mIsAsciiLowercase(aIsAsciiLowercase)
     , mHash(aHash)
   {}
 
   // Used by nsDynamicAtom.
-  nsAtom(const nsAString& aString, uint32_t aHash)
+  nsAtom(const nsAString& aString,
+         uint32_t aHash,
+         bool aIsAsciiLowercase)
     : mLength(aString.Length())
     , mIsStatic(false)
+    , mIsAsciiLowercase(aIsAsciiLowercase)
     , mHash(aHash)
   {
   }
 
   ~nsAtom() = default;
 
   const uint32_t mLength:30;
-  // NOTE: There's one free bit here.
   const uint32_t mIsStatic:1;
+  const uint32_t mIsAsciiLowercase:1;
   const uint32_t mHash;
 };
 
 // This class would be |final| if it wasn't for nsCSSAnonBoxPseudoStaticAtom
 // and nsCSSPseudoElementStaticAtom, which are trivial subclasses used to
 // ensure only certain static atoms are passed to certain functions.
 class nsStaticAtom : public nsAtom
 {
@@ -118,18 +127,18 @@ public:
   MozExternalRefCountType Release() = delete;
 
   // The static atom's precomputed hash value is an argument here, but it
   // must be the same as would be computed by mozilla::HashString(aStr),
   // which is what we use when atomizing strings. We compute this hash in
   // Atom.py and assert in nsAtomTable::RegisterStaticAtoms that the two
   // hashes match.
   constexpr nsStaticAtom(uint32_t aLength, uint32_t aHash,
-                         uint32_t aStringOffset)
-    : nsAtom(aLength, aHash)
+                         uint32_t aStringOffset, bool aIsAsciiLowercase)
+    : nsAtom(aLength, aHash, aIsAsciiLowercase)
     , mStringOffset(aStringOffset)
   {}
 
   const char16_t* String() const
   {
     return reinterpret_cast<const char16_t*>(uintptr_t(this) - mStringOffset);
   }
 
@@ -162,24 +171,20 @@ public:
   }
 
 private:
   friend class nsAtomTable;
   friend class nsAtomSubTable;
 
   // These shouldn't be used directly, even by friend classes. The
   // Create()/Destroy() methods use them.
-  static nsDynamicAtom* CreateInner(const nsAString& aString, uint32_t aHash);
-  nsDynamicAtom(const nsAString& aString, uint32_t aHash);
+  nsDynamicAtom(const nsAString& aString, uint32_t aHash, bool aIsAsciiLowercase);
   ~nsDynamicAtom() {}
 
-  // Creation/destruction is done by friend classes. The first Create() is for
-  // dynamic normal atoms, the second is for dynamic HTML5 atoms.
   static nsDynamicAtom* Create(const nsAString& aString, uint32_t aHash);
-  static nsDynamicAtom* Create(const nsAString& aString);
   static void Destroy(nsDynamicAtom* aAtom);
 
   mozilla::ThreadSafeAutoRefCnt mRefCnt;
 
   // The atom's chars are stored at the end of the struct.
 };
 
 // The four forms of NS_Atomize (for use with |RefPtr<nsAtom>|) return the
--- a/xpcom/ds/nsAtomTable.cpp
+++ b/xpcom/ds/nsAtomTable.cpp
@@ -61,54 +61,57 @@ enum class GCKind {
 // threads are operating the same atom, so it has to be signed so that
 // we wouldn't use overflow value for comparison.
 // See nsAtom::AddRef() and nsAtom::Release().
 // This atomic can be accessed during the GC and other places where recorded
 // events are not allowed, so its value is not preserved when recording or
 // replaying.
 static Atomic<int32_t, ReleaseAcquire, recordreplay::Behavior::DontPreserve> gUnusedAtomCount(0);
 
-nsDynamicAtom::nsDynamicAtom(const nsAString& aString, uint32_t aHash)
-  : nsAtom(aString, aHash)
+nsDynamicAtom::nsDynamicAtom(const nsAString& aString, uint32_t aHash, bool aIsAsciiLowercase)
+  : nsAtom(aString, aHash, aIsAsciiLowercase)
   , mRefCnt(1)
 {
 }
 
+// Returns true if ToLowercaseASCII would return the string unchanged.
+static bool
+IsAsciiLowercase(const char16_t* aString, const uint32_t aLength)
+{
+  for (uint32_t i = 0; i < aLength; ++i) {
+    if (IS_ASCII_UPPER(aString[i])) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
 nsDynamicAtom*
-nsDynamicAtom::CreateInner(const nsAString& aString, uint32_t aHash)
+nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash)
 {
   // We tack the chars onto the end of the nsDynamicAtom object.
   size_t numCharBytes = (aString.Length() + 1) * sizeof(char16_t);
   size_t numTotalBytes = sizeof(nsDynamicAtom) + numCharBytes;
 
+  bool isAsciiLower = ::IsAsciiLowercase(aString.Data(), aString.Length());
+
   nsDynamicAtom* atom = (nsDynamicAtom*)moz_xmalloc(numTotalBytes);
-  new (atom) nsDynamicAtom(aString, aHash);
+  new (atom) nsDynamicAtom(aString, aHash, isAsciiLower);
   memcpy(const_cast<char16_t*>(atom->String()),
          PromiseFlatString(aString).get(), numCharBytes);
 
   MOZ_ASSERT(atom->String()[atom->GetLength()] == char16_t(0));
   MOZ_ASSERT(atom->Equals(aString));
+  MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength()));
+  MOZ_ASSERT(atom->mIsAsciiLowercase == isAsciiLower);
 
   return atom;
 }
 
-nsDynamicAtom*
-nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash)
-{
-  nsDynamicAtom* atom = CreateInner(aString, aHash);
-  MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength()));
-  return atom;
-}
-
-nsDynamicAtom*
-nsDynamicAtom::Create(const nsAString& aString)
-{
-  return CreateInner(aString, /* hash */ 0);
-}
-
 void
 nsDynamicAtom::Destroy(nsDynamicAtom* aAtom)
 {
   aAtom->~nsDynamicAtom();
   free(aAtom);
 }
 
 const nsStaticAtom*
@@ -176,36 +179,32 @@ struct AtomTableKey
     : mUTF16String(aAtom->String())
     , mUTF8String(nullptr)
     , mLength(aAtom->GetLength())
     , mHash(aAtom->hash())
   {
     MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash);
   }
 
-  AtomTableKey(const char16_t* aUTF16String, uint32_t aLength,
-               uint32_t* aHashOut)
+  AtomTableKey(const char16_t* aUTF16String, uint32_t aLength)
     : mUTF16String(aUTF16String)
     , mUTF8String(nullptr)
     , mLength(aLength)
   {
     mHash = HashString(mUTF16String, mLength);
-    *aHashOut = mHash;
   }
 
   AtomTableKey(const char* aUTF8String,
                uint32_t aLength,
-               uint32_t* aHashOut,
                bool* aErr)
     : mUTF16String(nullptr)
     , mUTF8String(aUTF8String)
     , mLength(aLength)
   {
     mHash = HashUTF8AsUTF16(mUTF8String, mLength, aErr);
-    *aHashOut = mHash;
   }
 
   const char16_t* mUTF16String;
   const char* mUTF8String;
   uint32_t mLength;
   uint32_t mHash;
 };
 
@@ -636,16 +635,17 @@ nsAtomTable::RegisterStaticAtoms(const n
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!");
 
   for (uint32_t i = 0; i < aAtomsLen; ++i) {
     const nsStaticAtom* atom = &aAtoms[i];
     MOZ_ASSERT(nsCRT::IsAscii(atom->String()));
     MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength());
+    MOZ_ASSERT(atom->IsAsciiLowercase() == ::IsAsciiLowercase(atom->String(), atom->GetLength()));
 
     // This assertion ensures the static atom's precomputed hash value matches
     // what would be computed by mozilla::HashString(aStr), which is what we use
     // when atomizing strings. We compute this hash in Atom.py.
     MOZ_ASSERT(HashString(atom->String()) == atom->hash());
 
     AtomTableKey key(atom);
     nsAtomSubTable& table = SelectSubTable(key);
@@ -672,40 +672,38 @@ NS_Atomize(const char* aUTF8String)
 {
   MOZ_ASSERT(gAtomTable);
   return gAtomTable->Atomize(nsDependentCString(aUTF8String));
 }
 
 already_AddRefed<nsAtom>
 nsAtomTable::Atomize(const nsACString& aUTF8String)
 {
-  uint32_t hash;
   bool err;
-  AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &hash, &err);
+  AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &err);
   if (MOZ_UNLIKELY(err)) {
     MOZ_ASSERT_UNREACHABLE("Tried to atomize invalid UTF-8.");
     // The input was invalid UTF-8. Let's replace the errors with U+FFFD
     // and atomize the result.
     nsString str;
     CopyUTF8toUTF16(aUTF8String, str);
     return Atomize(str);
   }
   nsAtomSubTable& table = SelectSubTable(key);
   MutexAutoLock lock(table.mLock);
   AtomTableEntry* he = table.Add(key);
 
   if (he->mAtom) {
     RefPtr<nsAtom> atom = he->mAtom;
-
     return atom.forget();
   }
 
   nsString str;
   CopyUTF8toUTF16(aUTF8String, str);
-  RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, hash));
+  RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, key.mHash));
 
   he->mAtom = atom;
 
   return atom.forget();
 }
 
 already_AddRefed<nsAtom>
 NS_Atomize(const nsACString& aUTF8String)
@@ -719,29 +717,28 @@ NS_Atomize(const char16_t* aUTF16String)
 {
   MOZ_ASSERT(gAtomTable);
   return gAtomTable->Atomize(nsDependentString(aUTF16String));
 }
 
 already_AddRefed<nsAtom>
 nsAtomTable::Atomize(const nsAString& aUTF16String)
 {
-  uint32_t hash;
-  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
+  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
   nsAtomSubTable& table = SelectSubTable(key);
   MutexAutoLock lock(table.mLock);
   AtomTableEntry* he = table.Add(key);
 
   if (he->mAtom) {
     RefPtr<nsAtom> atom = he->mAtom;
-
     return atom.forget();
   }
 
-  RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(aUTF16String, hash));
+  RefPtr<nsAtom> atom =
+    dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash));
   he->mAtom = atom;
 
   return atom.forget();
 }
 
 already_AddRefed<nsAtom>
 NS_Atomize(const nsAString& aUTF16String)
 {
@@ -749,33 +746,32 @@ NS_Atomize(const nsAString& aUTF16String
   return gAtomTable->Atomize(aUTF16String);
 }
 
 already_AddRefed<nsAtom>
 nsAtomTable::AtomizeMainThread(const nsAString& aUTF16String)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<nsAtom> retVal;
-  uint32_t hash;
-  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
+  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
   auto p = sRecentlyUsedMainThreadAtoms.Lookup(key);
   if (p) {
     retVal = p.Data();
     return retVal.forget();
   }
 
   nsAtomSubTable& table = SelectSubTable(key);
   MutexAutoLock lock(table.mLock);
   AtomTableEntry* he = table.Add(key);
 
   if (he->mAtom) {
     retVal = he->mAtom;
   } else {
     RefPtr<nsAtom> newAtom =
-      dont_AddRef(nsDynamicAtom::Create(aUTF16String, hash));
+      dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash));
     he->mAtom = newAtom;
     retVal = newAtom.forget();
   }
 
   p.Set(retVal);
   return retVal.forget();
 }
 
@@ -805,39 +801,28 @@ NS_GetStaticAtom(const nsAString& aUTF16
   MOZ_ASSERT(gStaticAtomsDone, "Static atom setup not yet done.");
   MOZ_ASSERT(gAtomTable);
   return gAtomTable->GetStaticAtom(aUTF16String);
 }
 
 nsStaticAtom*
 nsAtomTable::GetStaticAtom(const nsAString& aUTF16String)
 {
-  uint32_t hash;
-  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
+  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
   nsAtomSubTable& table = SelectSubTable(key);
   MutexAutoLock lock(table.mLock);
   AtomTableEntry* he = table.Search(key);
   return he && he->mAtom->IsStatic()
        ? static_cast<nsStaticAtom*>(he->mAtom)
        : nullptr;
 }
 
 void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom)
 {
   // Assume the common case is that the atom is already ASCII lowercase.
-  bool reAtomize = false;
-  const nsDependentString existing(aAtom->GetUTF16String(), aAtom->GetLength());
-  for (size_t i = 0; i < existing.Length(); ++i) {
-    if (IS_ASCII_UPPER(existing[i])) {
-      reAtomize = true;
-      break;
-    }
-  }
-
-  // If the string was already lowercase, we're done.
-  if (!reAtomize) {
+  if (aAtom->IsAsciiLowercase()) {
     return;
   }
 
   nsAutoString lowercased;
-  ToLowerCaseASCII(existing, lowercased);
+  ToLowerCaseASCII(nsDependentAtomString(aAtom), lowercased);
   aAtom = NS_Atomize(lowercased);
 }
--- a/xpcom/ds/nsGkAtoms.cpp
+++ b/xpcom/ds/nsGkAtoms.cpp
@@ -15,17 +15,17 @@ extern constexpr GkAtoms gGkAtoms = {
   // The initialization of each atom's string.
   //
   // Expansion of the example GK_ATOM entries in nsGkAtoms.h:
   //
   //   u"a",
   //   u"bb",
   //   u"ccc",
   //
-  #define GK_ATOM(name_, value_, hash_, type_, atom_type_) \
+  #define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
     u"" value_,
   #include "nsGkAtomList.h"
   #undef GK_ATOM
   {
     // The initialization of the atoms themselves.
     //
     // Note that |value_| is an 8-bit string, and so |sizeof(value_)| is equal
     // to the number of chars (including the terminating '\0'). The |u""| prefix
@@ -43,22 +43,22 @@ extern constexpr GkAtoms gGkAtoms = {
     //     offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::bb)]) -
     //     offsetof(GkAtoms, bb_string)),
     //
     //   nsStaticAtom(
     //     3, 0x23456789,
     //     offsetof(GkAtoms, mAtoms[static_cast<size_t>(GkAtoms::Atoms::ccc)]) -
     //     offsetof(GkAtoms, ccc_string)),
     //
-    #define GK_ATOM(name_, value_, hash_, type_, atom_type_)                   \
+    #define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_)  \
       nsStaticAtom(                                                            \
         sizeof(value_) - 1, hash_,                                             \
         offsetof(GkAtoms,                                                      \
                  mAtoms[static_cast<size_t>(GkAtoms::Atoms::name_)]) -         \
-        offsetof(GkAtoms, name_##_string)),
+        offsetof(GkAtoms, name_##_string), is_ascii_lower_),
     #include "nsGkAtomList.h"
     #undef GK_ATOM
   }
 };
 
 } // namespace detail
 } // namespace mozilla
 
--- a/xpcom/ds/nsGkAtoms.h
+++ b/xpcom/ds/nsGkAtoms.h
@@ -53,18 +53,19 @@
 // expanded by macros.
 
 // Trivial subclasses of nsStaticAtom so that function signatures can require
 // an atom from a specific atom list.
 #define DEFINE_STATIC_ATOM_SUBCLASS(name_)                                     \
   class name_ : public nsStaticAtom                                            \
   {                                                                            \
   public:                                                                      \
-    constexpr name_(uint32_t aLength, uint32_t aHash, uint32_t aOffset)        \
-      : nsStaticAtom(aLength, aHash, aOffset) {}                               \
+    constexpr name_(uint32_t aLength, uint32_t aHash, uint32_t aOffset,        \
+                    bool aIsAsciiLowercase)                                    \
+      : nsStaticAtom(aLength, aHash, aOffset, aIsAsciiLowercase) {}            \
   };
 
 DEFINE_STATIC_ATOM_SUBCLASS(nsCSSAnonBoxPseudoStaticAtom)
 DEFINE_STATIC_ATOM_SUBCLASS(nsCSSPseudoElementStaticAtom)
 
 #undef DEFINE_STATIC_ATOM_SUBCLASS
 
 namespace mozilla {
@@ -81,30 +82,30 @@ struct GkAtoms
   // The declaration of each atom's string.
   //
   // Expansion of the example GK_ATOM entries from above:
   //
   //   const char16_t a_string[sizeof("a")];
   //   const char16_t bb_string[sizeof("bb")];
   //   const char16_t ccc_string[sizeof("ccc")];
   //
-  #define GK_ATOM(name_, value_, hash_, type_, atom_type_) \
+  #define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
     const char16_t name_##_string[sizeof(value_)];
   #include "nsGkAtomList.h"
   #undef GK_ATOM
 
   // The enum value for each atom.
   enum class Atoms {
     // Expansion of the example GK_ATOM entries above:
     //
     //   a,
     //   bb,
     //   ccc,
     //
-    #define GK_ATOM(name_, value_, hash_, type_, atom_type_) \
+    #define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \
       name_,
     #include "nsGkAtomList.h"
     #undef GK_ATOM
     AtomsCount
   };
 
   const nsStaticAtom mAtoms[static_cast<size_t>(Atoms::AtomsCount)];
 };
@@ -161,17 +162,17 @@ public:
   //       &mozilla::detail::gGkAtoms.mAtoms[
   //         static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::bb)]);
   //
   //   static constexpr nsStaticAtom* ccc =
   //     const_cast<nsStaticAtom*>(
   //       &mozilla::detail::gGkAtoms.mAtoms[
   //         static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::ccc)]);
   //
-  #define GK_ATOM(name_, value_, hash_, type_, atom_type_)                    \
+  #define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_)   \
     static constexpr nsStaticAtom* name_ =                                    \
       const_cast<nsStaticAtom*>(                                              \
         &mozilla::detail::gGkAtoms.mAtoms[                                    \
           static_cast<size_t>(mozilla::detail::GkAtoms::Atoms::name_)]);
   #include "nsGkAtomList.h"
   #undef GK_ATOM
 };