Bug 1860492 - Change file name in test @ toolkit/components/antitracking/test/browser...
[gecko.git] / xpcom / ds / nsAtomTable.cpp
bloba03fdcce532d9dd000ea4d5cb5449843432f606a
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Assertions.h"
8 #include "mozilla/Attributes.h"
9 #include "mozilla/HashFunctions.h"
10 #include "mozilla/MemoryReporting.h"
11 #include "mozilla/MruCache.h"
12 #include "mozilla/RWLock.h"
13 #include "mozilla/TextUtils.h"
14 #include "nsHashKeys.h"
15 #include "nsThreadUtils.h"
17 #include "nsAtom.h"
18 #include "nsAtomTable.h"
19 #include "nsGkAtoms.h"
20 #include "nsPrintfCString.h"
21 #include "nsString.h"
22 #include "nsUnicharUtils.h"
23 #include "PLDHashTable.h"
24 #include "prenv.h"
26 // There are two kinds of atoms handled by this module.
28 // - Dynamic: the atom itself is heap allocated, as is the char buffer it
29 // points to. |gAtomTable| holds weak references to dynamic atoms. When the
30 // refcount of a dynamic atom drops to zero, we increment a static counter.
31 // When that counter reaches a certain threshold, we iterate over the atom
32 // table, removing and deleting dynamic atoms with refcount zero. This allows
33 // us to avoid acquiring the atom table lock during normal refcounting.
35 // - Static: both the atom and its chars are statically allocated and
36 // immutable, so it ignores all AddRef/Release calls.
38 // Note that gAtomTable is used on multiple threads, and has internal
39 // synchronization.
41 using namespace mozilla;
43 //----------------------------------------------------------------------
45 enum class GCKind {
46 RegularOperation,
47 Shutdown,
50 //----------------------------------------------------------------------
52 // gUnusedAtomCount is incremented when an atom loses its last reference
53 // (and thus turned into unused state), and decremented when an unused
54 // atom gets a reference again. The atom table relies on this value to
55 // schedule GC. This value can temporarily go below zero when multiple
56 // threads are operating the same atom, so it has to be signed so that
57 // we wouldn't use overflow value for comparison.
58 // See nsAtom::AddRef() and nsAtom::Release().
59 // This atomic can be accessed during the GC and other places where recorded
60 // events are not allowed, so its value is not preserved when recording or
61 // replaying.
62 Atomic<int32_t, ReleaseAcquire> nsDynamicAtom::gUnusedAtomCount;
64 nsDynamicAtom::nsDynamicAtom(already_AddRefed<nsStringBuffer> aBuffer,
65 uint32_t aLength, uint32_t aHash,
66 bool aIsAsciiLowercase)
67 : nsAtom(aLength, /* aIsStatic = */ false, aHash, aIsAsciiLowercase),
68 mRefCnt(1),
69 mStringBuffer(aBuffer) {}
71 // Returns true if ToLowercaseASCII would return the string unchanged.
72 static bool IsAsciiLowercase(const char16_t* aString, const uint32_t aLength) {
73 for (uint32_t i = 0; i < aLength; ++i) {
74 if (IS_ASCII_UPPER(aString[i])) {
75 return false;
78 return true;
81 nsDynamicAtom* nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash) {
82 // We tack the chars onto the end of the nsDynamicAtom object.
83 const bool isAsciiLower =
84 ::IsAsciiLowercase(aString.Data(), aString.Length());
85 RefPtr<nsStringBuffer> buffer = nsStringBuffer::FromString(aString);
86 if (!buffer) {
87 buffer = nsStringBuffer::Create(aString.Data(), aString.Length());
88 if (MOZ_UNLIKELY(!buffer)) {
89 MOZ_CRASH("Out of memory atomizing");
91 } else {
92 MOZ_ASSERT(aString.IsTerminated(),
93 "String buffers are always null-terminated");
95 auto* atom =
96 new nsDynamicAtom(buffer.forget(), aString.Length(), aHash, isAsciiLower);
97 MOZ_ASSERT(atom->String()[atom->GetLength()] == char16_t(0));
98 MOZ_ASSERT(atom->Equals(aString));
99 MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength()));
100 MOZ_ASSERT(atom->mIsAsciiLowercase == isAsciiLower);
101 return atom;
104 void nsDynamicAtom::Destroy(nsDynamicAtom* aAtom) { delete aAtom; }
106 void nsAtom::ToString(nsAString& aString) const {
107 // See the comment on |mString|'s declaration.
108 if (IsStatic()) {
109 // AssignLiteral() lets us assign without copying. This isn't a string
110 // literal, but it's a static atom and thus has an unbounded lifetime,
111 // which is what's important.
112 aString.AssignLiteral(AsStatic()->String(), mLength);
113 } else {
114 AsDynamic()->StringBuffer()->ToString(mLength, aString);
118 void nsAtom::ToUTF8String(nsACString& aBuf) const {
119 CopyUTF16toUTF8(nsDependentString(GetUTF16String(), mLength), aBuf);
122 void nsAtom::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
123 AtomsSizes& aSizes) const {
124 // Static atoms are in static memory, and so are not measured here.
125 if (IsDynamic()) {
126 aSizes.mDynamicAtoms += aMallocSizeOf(this);
130 char16ptr_t nsAtom::GetUTF16String() const {
131 return IsStatic() ? AsStatic()->String() : AsDynamic()->String();
134 //----------------------------------------------------------------------
136 struct AtomTableKey {
137 explicit AtomTableKey(const nsStaticAtom* aAtom)
138 : mUTF16String(aAtom->String()),
139 mUTF8String(nullptr),
140 mLength(aAtom->GetLength()),
141 mHash(aAtom->hash()) {
142 MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash);
145 AtomTableKey(const char16_t* aUTF16String, uint32_t aLength, uint32_t aHash)
146 : mUTF16String(aUTF16String),
147 mUTF8String(nullptr),
148 mLength(aLength),
149 mHash(aHash) {
150 MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash);
153 AtomTableKey(const char16_t* aUTF16String, uint32_t aLength)
154 : AtomTableKey(aUTF16String, aLength, HashString(aUTF16String, aLength)) {
157 AtomTableKey(const char* aUTF8String, uint32_t aLength, bool* aErr)
158 : mUTF16String(nullptr), mUTF8String(aUTF8String), mLength(aLength) {
159 mHash = HashUTF8AsUTF16(mUTF8String, mLength, aErr);
162 const char16_t* mUTF16String;
163 const char* mUTF8String;
164 uint32_t mLength;
165 uint32_t mHash;
168 struct AtomTableEntry : public PLDHashEntryHdr {
169 // These references are either to dynamic atoms, in which case they are
170 // non-owning, or they are to static atoms, which aren't really refcounted.
171 // See the comment at the top of this file for more details.
172 nsAtom* MOZ_NON_OWNING_REF mAtom;
175 struct AtomCache : public MruCache<AtomTableKey, nsAtom*, AtomCache> {
176 static HashNumber Hash(const AtomTableKey& aKey) { return aKey.mHash; }
177 static bool Match(const AtomTableKey& aKey, const nsAtom* aVal) {
178 MOZ_ASSERT(aKey.mUTF16String);
179 return aVal->Equals(aKey.mUTF16String, aKey.mLength);
183 static AtomCache sRecentlyUsedSmallMainThreadAtoms;
184 static AtomCache sRecentlyUsedLargeMainThreadAtoms;
186 // In order to reduce locking contention for concurrent atomization, we segment
187 // the atom table into N subtables, each with a separate lock. If the hash
188 // values we use to select the subtable are evenly distributed, this reduces the
189 // probability of contention by a factor of N. See bug 1440824.
191 // NB: This is somewhat similar to the technique used by Java's
192 // ConcurrentHashTable.
193 class nsAtomSubTable {
194 friend class nsAtomTable;
195 mozilla::RWLock mLock;
196 PLDHashTable mTable;
197 nsAtomSubTable();
198 void GCLocked(GCKind aKind) MOZ_REQUIRES(mLock);
199 void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
200 AtomsSizes& aSizes)
201 MOZ_REQUIRES_SHARED(mLock);
203 AtomTableEntry* Search(AtomTableKey& aKey) const MOZ_REQUIRES_SHARED(mLock) {
204 // XXX There's no LockedForReadingByCurrentThread();
205 return static_cast<AtomTableEntry*>(mTable.Search(&aKey));
208 AtomTableEntry* Add(AtomTableKey& aKey) MOZ_REQUIRES(mLock) {
209 MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
210 return static_cast<AtomTableEntry*>(mTable.Add(&aKey)); // Infallible
214 // The outer atom table, which coordinates access to the inner array of
215 // subtables.
216 class nsAtomTable {
217 public:
218 nsAtomSubTable& SelectSubTable(AtomTableKey& aKey);
219 void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes);
220 void GC(GCKind aKind);
221 already_AddRefed<nsAtom> Atomize(const nsAString& aUTF16String,
222 uint32_t aHash);
223 already_AddRefed<nsAtom> Atomize(const nsACString& aUTF8String);
224 already_AddRefed<nsAtom> AtomizeMainThread(const nsAString& aUTF16String);
225 nsStaticAtom* GetStaticAtom(const nsAString& aUTF16String);
226 void RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen);
228 // The result of this function may be imprecise if other threads are operating
229 // on atoms concurrently. It's also slow, since it triggers a GC before
230 // counting.
231 size_t RacySlowCount();
233 // This hash table op is a static member of this class so that it can take
234 // advantage of |friend| declarations.
235 static void AtomTableClearEntry(PLDHashTable* aTable,
236 PLDHashEntryHdr* aEntry);
238 // We achieve measurable reduction in locking contention in parallel CSS
239 // parsing by increasing the number of subtables up to 128. This has been
240 // measured to have neglible impact on the performance of initialization, GC,
241 // and shutdown.
243 // Another important consideration is memory, since we're adding fixed
244 // overhead per content process, which we try to avoid. Measuring a
245 // mostly-empty page [1] with various numbers of subtables, we get the
246 // following deep sizes for the atom table:
247 // 1 subtable: 278K
248 // 8 subtables: 279K
249 // 16 subtables: 282K
250 // 64 subtables: 286K
251 // 128 subtables: 290K
253 // So 128 subtables costs us 12K relative to a single table, and 4K relative
254 // to 64 subtables. Conversely, measuring parallel (6 thread) CSS parsing on
255 // tp6-facebook, a single table provides ~150ms of locking overhead per
256 // thread, 64 subtables provides ~2-3ms of overhead, and 128 subtables
257 // provides <1ms. And so while either 64 or 128 subtables would probably be
258 // acceptable, achieving a measurable reduction in contention for 4k of fixed
259 // memory overhead is probably worth it.
261 // [1] The numbers will look different for content processes with complex
262 // pages loaded, but in those cases the actual atoms will dominate memory
263 // usage and the overhead of extra tables will be negligible. We're mostly
264 // interested in the fixed cost for nearly-empty content processes.
265 constexpr static size_t kNumSubTables = 512; // Must be power of two.
267 // The atom table very quickly gets 10,000+ entries in it (or even 100,000+).
268 // But choosing the best initial subtable length has some subtleties: we add
269 // ~2700 static atoms at start-up, and then we start adding and removing
270 // dynamic atoms. If we make the tables too big to start with, when the first
271 // dynamic atom gets removed from a given table the load factor will be < 25%
272 // and we will shrink it.
274 // So we first make the simplifying assumption that the atoms are more or less
275 // evenly-distributed across the subtables (which is the case empirically).
276 // Then, we take the total atom count when the first dynamic atom is removed
277 // (~2700), divide that across the N subtables, and the largest capacity that
278 // will allow each subtable to be > 25% full with that count.
280 // So want an initial subtable capacity less than (2700 / N) * 4 = 10800 / N.
281 // Rounding down to the nearest power of two gives us 8192 / N. Since the
282 // capacity is double the initial length, we end up with (4096 / N) per
283 // subtable.
284 constexpr static size_t kInitialSubTableSize = 4096 / kNumSubTables;
286 private:
287 nsAtomSubTable mSubTables[kNumSubTables];
290 // Static singleton instance for the atom table.
291 static nsAtomTable* gAtomTable;
293 static PLDHashNumber AtomTableGetHash(const void* aKey) {
294 const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
295 return k->mHash;
298 static bool AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey) {
299 const AtomTableEntry* he = static_cast<const AtomTableEntry*>(aEntry);
300 const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
302 if (k->mUTF8String) {
303 bool err = false;
304 return (CompareUTF8toUTF16(nsDependentCSubstring(
305 k->mUTF8String, k->mUTF8String + k->mLength),
306 nsDependentAtomString(he->mAtom), &err) == 0) &&
307 !err;
310 return he->mAtom->Equals(k->mUTF16String, k->mLength);
313 void nsAtomTable::AtomTableClearEntry(PLDHashTable* aTable,
314 PLDHashEntryHdr* aEntry) {
315 auto* entry = static_cast<AtomTableEntry*>(aEntry);
316 entry->mAtom = nullptr;
319 static void AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) {
320 static_cast<AtomTableEntry*>(aEntry)->mAtom = nullptr;
323 static const PLDHashTableOps AtomTableOps = {
324 AtomTableGetHash, AtomTableMatchKey, PLDHashTable::MoveEntryStub,
325 nsAtomTable::AtomTableClearEntry, AtomTableInitEntry};
327 nsAtomSubTable& nsAtomTable::SelectSubTable(AtomTableKey& aKey) {
328 // There are a few considerations around how we select subtables.
330 // First, we want entries to be evenly distributed across the subtables. This
331 // can be achieved by using any bits in the hash key, assuming the key itself
332 // is evenly-distributed. Empirical measurements indicate that this method
333 // produces a roughly-even distribution across subtables.
335 // Second, we want to use the hash bits that are least likely to influence an
336 // entry's position within the subtable. If we used the exact same bits used
337 // by the subtables, then each subtable would compute the same position for
338 // every entry it observes, leading to pessimal performance. In this case,
339 // we're using PLDHashTable, whose primary hash function uses the N leftmost
340 // bits of the hash value (where N is the log2 capacity of the table). This
341 // means we should prefer the rightmost bits here.
343 // Note that the below is equivalent to mHash % kNumSubTables, a replacement
344 // which an optimizing compiler should make, but let's avoid any doubt.
345 static_assert((kNumSubTables & (kNumSubTables - 1)) == 0,
346 "must be power of two");
347 return mSubTables[aKey.mHash & (kNumSubTables - 1)];
350 void nsAtomTable::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
351 AtomsSizes& aSizes) {
352 MOZ_ASSERT(NS_IsMainThread());
353 aSizes.mTable += aMallocSizeOf(this);
354 for (auto& table : mSubTables) {
355 AutoReadLock lock(table.mLock);
356 table.AddSizeOfExcludingThisLocked(aMallocSizeOf, aSizes);
360 void nsAtomTable::GC(GCKind aKind) {
361 MOZ_ASSERT(NS_IsMainThread());
362 sRecentlyUsedSmallMainThreadAtoms.Clear();
363 sRecentlyUsedLargeMainThreadAtoms.Clear();
365 // Note that this is effectively an incremental GC, since only one subtable
366 // is locked at a time.
367 for (auto& table : mSubTables) {
368 AutoWriteLock lock(table.mLock);
369 table.GCLocked(aKind);
372 // We would like to assert that gUnusedAtomCount matches the number of atoms
373 // we found in the table which we removed. However, there are two problems
374 // with this:
375 // * We have multiple subtables, each with their own lock. For optimal
376 // performance we only want to hold one lock at a time, but this means
377 // that atoms can be added and removed between GC slices.
378 // * Even if we held all the locks and performed all GC slices atomically,
379 // the locks are not acquired for AddRef() and Release() calls. This means
380 // we might see a gUnusedAtomCount value in between, say, AddRef()
381 // incrementing mRefCnt and it decrementing gUnusedAtomCount.
383 // So, we don't bother asserting that there are no unused atoms at the end of
384 // a regular GC. But we can (and do) assert this just after the last GC at
385 // shutdown.
387 // Note that, barring refcounting bugs, an atom can only go from a zero
388 // refcount to a non-zero refcount while the atom table lock is held, so
389 // so we won't try to resurrect a zero refcount atom while trying to delete
390 // it.
392 MOZ_ASSERT_IF(aKind == GCKind::Shutdown,
393 nsDynamicAtom::gUnusedAtomCount == 0);
396 size_t nsAtomTable::RacySlowCount() {
397 // Trigger a GC so that the result is deterministic modulo other threads.
398 GC(GCKind::RegularOperation);
399 size_t count = 0;
400 for (auto& table : mSubTables) {
401 AutoReadLock lock(table.mLock);
402 count += table.mTable.EntryCount();
405 return count;
408 nsAtomSubTable::nsAtomSubTable()
409 : mLock("Atom Sub-Table Lock"),
410 mTable(&AtomTableOps, sizeof(AtomTableEntry),
411 nsAtomTable::kInitialSubTableSize) {}
413 void nsAtomSubTable::GCLocked(GCKind aKind) {
414 MOZ_ASSERT(NS_IsMainThread());
415 MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
417 int32_t removedCount = 0; // A non-atomic temporary for cheaper increments.
418 nsAutoCString nonZeroRefcountAtoms;
419 uint32_t nonZeroRefcountAtomsCount = 0;
420 for (auto i = mTable.Iter(); !i.Done(); i.Next()) {
421 auto* entry = static_cast<AtomTableEntry*>(i.Get());
422 if (entry->mAtom->IsStatic()) {
423 continue;
426 nsAtom* atom = entry->mAtom;
427 if (atom->IsDynamic() && atom->AsDynamic()->mRefCnt == 0) {
428 i.Remove();
429 nsDynamicAtom::Destroy(atom->AsDynamic());
430 ++removedCount;
432 #ifdef NS_FREE_PERMANENT_DATA
433 else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) {
434 // Only report leaking atoms in leak-checking builds in a run where we
435 // are checking for leaks, during shutdown. If something is anomalous,
436 // then we'll assert later in this function.
437 nsAutoCString name;
438 atom->ToUTF8String(name);
439 if (nonZeroRefcountAtomsCount == 0) {
440 nonZeroRefcountAtoms = name;
441 } else if (nonZeroRefcountAtomsCount < 20) {
442 nonZeroRefcountAtoms += ","_ns + name;
443 } else if (nonZeroRefcountAtomsCount == 20) {
444 nonZeroRefcountAtoms += ",..."_ns;
446 nonZeroRefcountAtomsCount++;
448 #endif
450 if (nonZeroRefcountAtomsCount) {
451 nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s",
452 nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get());
453 NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get());
456 nsDynamicAtom::gUnusedAtomCount -= removedCount;
459 void nsDynamicAtom::GCAtomTable() {
460 MOZ_ASSERT(gAtomTable);
461 if (NS_IsMainThread()) {
462 gAtomTable->GC(GCKind::RegularOperation);
466 //----------------------------------------------------------------------
468 // Have the static atoms been inserted into the table?
469 static bool gStaticAtomsDone = false;
471 void NS_InitAtomTable() {
472 MOZ_ASSERT(NS_IsMainThread());
473 MOZ_ASSERT(!gAtomTable);
475 // We register static atoms immediately so they're available for use as early
476 // as possible.
477 gAtomTable = new nsAtomTable();
478 gAtomTable->RegisterStaticAtoms(nsGkAtoms::sAtoms, nsGkAtoms::sAtomsLen);
479 gStaticAtomsDone = true;
482 void NS_ShutdownAtomTable() {
483 MOZ_ASSERT(NS_IsMainThread());
484 MOZ_ASSERT(gAtomTable);
486 #ifdef NS_FREE_PERMANENT_DATA
487 // Do a final GC to satisfy leak checking. We skip this step in release
488 // builds.
489 gAtomTable->GC(GCKind::Shutdown);
490 #endif
492 delete gAtomTable;
493 gAtomTable = nullptr;
496 void NS_AddSizeOfAtoms(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes) {
497 MOZ_ASSERT(NS_IsMainThread());
498 MOZ_ASSERT(gAtomTable);
499 return gAtomTable->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
502 void nsAtomSubTable::AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
503 AtomsSizes& aSizes) {
504 aSizes.mTable += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
505 for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
506 auto* entry = static_cast<AtomTableEntry*>(iter.Get());
507 entry->mAtom->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
511 void nsAtomTable::RegisterStaticAtoms(const nsStaticAtom* aAtoms,
512 size_t aAtomsLen) {
513 MOZ_ASSERT(NS_IsMainThread());
514 MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!");
516 for (uint32_t i = 0; i < aAtomsLen; ++i) {
517 const nsStaticAtom* atom = &aAtoms[i];
518 MOZ_ASSERT(IsAsciiNullTerminated(atom->String()));
519 MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength());
520 MOZ_ASSERT(atom->IsAsciiLowercase() ==
521 ::IsAsciiLowercase(atom->String(), atom->GetLength()));
523 // This assertion ensures the static atom's precomputed hash value matches
524 // what would be computed by mozilla::HashString(aStr), which is what we use
525 // when atomizing strings. We compute this hash in Atom.py.
526 MOZ_ASSERT(HashString(atom->String()) == atom->hash());
528 AtomTableKey key(atom);
529 nsAtomSubTable& table = SelectSubTable(key);
530 AutoWriteLock lock(table.mLock);
531 AtomTableEntry* he = table.Add(key);
532 if (he->mAtom) {
533 // There are two ways we could get here.
534 // - Register two static atoms with the same string.
535 // - Create a dynamic atom and then register a static atom with the same
536 // string while the dynamic atom is alive.
537 // Both cases can cause subtle bugs, and are disallowed. We're
538 // programming in C++ here, not Smalltalk.
539 nsAutoCString name;
540 he->mAtom->ToUTF8String(name);
541 MOZ_CRASH_UNSAFE_PRINTF("Atom for '%s' already exists", name.get());
543 he->mAtom = const_cast<nsStaticAtom*>(atom);
547 already_AddRefed<nsAtom> NS_Atomize(const char* aUTF8String) {
548 MOZ_ASSERT(gAtomTable);
549 return gAtomTable->Atomize(nsDependentCString(aUTF8String));
552 already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsACString& aUTF8String) {
553 bool err;
554 AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &err);
555 if (MOZ_UNLIKELY(err)) {
556 MOZ_ASSERT_UNREACHABLE("Tried to atomize invalid UTF-8.");
557 // The input was invalid UTF-8. Let's replace the errors with U+FFFD
558 // and atomize the result.
559 nsString str;
560 CopyUTF8toUTF16(aUTF8String, str);
561 return Atomize(str, HashString(str));
563 nsAtomSubTable& table = SelectSubTable(key);
565 AutoReadLock lock(table.mLock);
566 if (AtomTableEntry* he = table.Search(key)) {
567 return do_AddRef(he->mAtom);
571 AutoWriteLock lock(table.mLock);
572 AtomTableEntry* he = table.Add(key);
574 if (he->mAtom) {
575 return do_AddRef(he->mAtom);
578 nsString str;
579 CopyUTF8toUTF16(aUTF8String, str);
580 MOZ_ASSERT(nsStringBuffer::FromString(str), "Should create a string buffer");
581 RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, key.mHash));
583 he->mAtom = atom;
585 return atom.forget();
588 already_AddRefed<nsAtom> NS_Atomize(const nsACString& aUTF8String) {
589 MOZ_ASSERT(gAtomTable);
590 return gAtomTable->Atomize(aUTF8String);
593 already_AddRefed<nsAtom> NS_Atomize(const char16_t* aUTF16String) {
594 return NS_Atomize(nsDependentString(aUTF16String));
597 already_AddRefed<nsAtom> nsAtomTable::Atomize(const nsAString& aUTF16String,
598 uint32_t aHash) {
599 AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), aHash);
600 nsAtomSubTable& table = SelectSubTable(key);
602 AutoReadLock lock(table.mLock);
603 if (AtomTableEntry* he = table.Search(key)) {
604 return do_AddRef(he->mAtom);
607 AutoWriteLock lock(table.mLock);
608 AtomTableEntry* he = table.Add(key);
610 if (he->mAtom) {
611 RefPtr<nsAtom> atom = he->mAtom;
612 return atom.forget();
615 RefPtr<nsAtom> atom =
616 dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash));
617 he->mAtom = atom;
619 return atom.forget();
622 already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String,
623 uint32_t aKnownHash) {
624 MOZ_ASSERT(gAtomTable);
625 return gAtomTable->Atomize(aUTF16String, aKnownHash);
628 already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String) {
629 return NS_Atomize(aUTF16String, HashString(aUTF16String));
632 already_AddRefed<nsAtom> nsAtomTable::AtomizeMainThread(
633 const nsAString& aUTF16String) {
634 MOZ_ASSERT(NS_IsMainThread());
635 RefPtr<nsAtom> retVal;
636 size_t length = aUTF16String.Length();
637 AtomTableKey key(aUTF16String.Data(), length);
639 auto p = (length < 5) ? sRecentlyUsedSmallMainThreadAtoms.Lookup(key)
640 : sRecentlyUsedLargeMainThreadAtoms.Lookup(key);
641 if (p) {
642 retVal = p.Data();
643 return retVal.forget();
646 nsAtomSubTable& table = SelectSubTable(key);
648 AutoReadLock lock(table.mLock);
649 if (AtomTableEntry* he = table.Search(key)) {
650 p.Set(he->mAtom);
651 return do_AddRef(he->mAtom);
655 AutoWriteLock lock(table.mLock);
656 AtomTableEntry* he = table.Add(key);
657 if (he->mAtom) {
658 retVal = he->mAtom;
659 } else {
660 RefPtr<nsAtom> newAtom =
661 dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash));
662 he->mAtom = newAtom;
663 retVal = std::move(newAtom);
666 p.Set(retVal);
667 return retVal.forget();
670 already_AddRefed<nsAtom> NS_AtomizeMainThread(const nsAString& aUTF16String) {
671 MOZ_ASSERT(gAtomTable);
672 return gAtomTable->AtomizeMainThread(aUTF16String);
675 nsrefcnt NS_GetNumberOfAtoms(void) {
676 MOZ_ASSERT(gAtomTable);
677 return gAtomTable->RacySlowCount();
680 int32_t NS_GetUnusedAtomCount(void) { return nsDynamicAtom::gUnusedAtomCount; }
682 nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String) {
683 MOZ_ASSERT(gStaticAtomsDone, "Static atom setup not yet done.");
684 MOZ_ASSERT(gAtomTable);
685 return gAtomTable->GetStaticAtom(aUTF16String);
688 nsStaticAtom* nsAtomTable::GetStaticAtom(const nsAString& aUTF16String) {
689 AtomTableKey key(aUTF16String.Data(), aUTF16String.Length());
690 nsAtomSubTable& table = SelectSubTable(key);
691 AutoReadLock lock(table.mLock);
692 AtomTableEntry* he = table.Search(key);
693 return he && he->mAtom->IsStatic() ? static_cast<nsStaticAtom*>(he->mAtom)
694 : nullptr;
697 void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom) {
698 // Assume the common case is that the atom is already ASCII lowercase.
699 if (aAtom->IsAsciiLowercase()) {
700 return;
703 nsAutoString lowercased;
704 ToLowerCaseASCII(nsDependentAtomString(aAtom), lowercased);
705 aAtom = NS_Atomize(lowercased);