Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/xpcom/ds/nsAtomTable.cpp
Line
Count
Source (jump to first uncovered line)
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/. */
6
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/Mutex.h"
13
#include "mozilla/DebugOnly.h"
14
#include "mozilla/Sprintf.h"
15
#include "mozilla/Unused.h"
16
17
#include "nsAtom.h"
18
#include "nsAtomTable.h"
19
#include "nsAutoPtr.h"
20
#include "nsCRT.h"
21
#include "nsDataHashtable.h"
22
#include "nsGkAtoms.h"
23
#include "nsHashKeys.h"
24
#include "nsPrintfCString.h"
25
#include "nsString.h"
26
#include "nsThreadUtils.h"
27
#include "nsUnicharUtils.h"
28
#include "PLDHashTable.h"
29
#include "prenv.h"
30
31
// There are two kinds of atoms handled by this module.
32
//
33
// - Dynamic: the atom itself is heap allocated, as is the char buffer it
34
//   points to. |gAtomTable| holds weak references to dynamic atoms. When the
35
//   refcount of a dynamic atom drops to zero, we increment a static counter.
36
//   When that counter reaches a certain threshold, we iterate over the atom
37
//   table, removing and deleting dynamic atoms with refcount zero. This allows
38
//   us to avoid acquiring the atom table lock during normal refcounting.
39
//
40
// - Static: both the atom and its chars are statically allocated and
41
//   immutable, so it ignores all AddRef/Release calls.
42
//
43
// Note that gAtomTable is used on multiple threads, and has internal
44
// synchronization.
45
46
using namespace mozilla;
47
48
//----------------------------------------------------------------------
49
50
enum class GCKind {
51
  RegularOperation,
52
  Shutdown,
53
};
54
55
//----------------------------------------------------------------------
56
57
// gUnusedAtomCount is incremented when an atom loses its last reference
58
// (and thus turned into unused state), and decremented when an unused
59
// atom gets a reference again. The atom table relies on this value to
60
// schedule GC. This value can temporarily go below zero when multiple
61
// threads are operating the same atom, so it has to be signed so that
62
// we wouldn't use overflow value for comparison.
63
// See nsAtom::AddRef() and nsAtom::Release().
64
// This atomic can be accessed during the GC and other places where recorded
65
// events are not allowed, so its value is not preserved when recording or
66
// replaying.
67
static Atomic<int32_t, ReleaseAcquire, recordreplay::Behavior::DontPreserve> gUnusedAtomCount(0);
68
69
nsDynamicAtom::nsDynamicAtom(const nsAString& aString, uint32_t aHash)
70
  : nsAtom(AtomKind::DynamicNormal, aString, aHash)
71
  , mRefCnt(1)
72
37
{
73
37
}
74
75
nsDynamicAtom*
76
nsDynamicAtom::CreateInner(const nsAString& aString, uint32_t aHash)
77
37
{
78
37
  // We tack the chars onto the end of the nsDynamicAtom object.
79
37
  size_t numCharBytes = (aString.Length() + 1) * sizeof(char16_t);
80
37
  size_t numTotalBytes = sizeof(nsDynamicAtom) + numCharBytes;
81
37
82
37
  nsDynamicAtom* atom = (nsDynamicAtom*)moz_xmalloc(numTotalBytes);
83
37
  new (atom) nsDynamicAtom(aString, aHash);
84
37
  memcpy(const_cast<char16_t*>(atom->String()),
85
37
         PromiseFlatString(aString).get(), numCharBytes);
86
37
87
37
  MOZ_ASSERT(atom->String()[atom->GetLength()] == char16_t(0));
88
37
  MOZ_ASSERT(atom->Equals(aString));
89
37
90
37
  return atom;
91
37
}
92
93
nsDynamicAtom*
94
nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash)
95
37
{
96
37
  nsDynamicAtom* atom = CreateInner(aString, aHash);
97
37
  MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength()));
98
37
  return atom;
99
37
}
100
101
nsDynamicAtom*
102
nsDynamicAtom::Create(const nsAString& aString)
103
0
{
104
0
  return CreateInner(aString, /* hash */ 0);
105
0
}
106
107
void
108
nsDynamicAtom::Destroy(nsDynamicAtom* aAtom)
109
0
{
110
0
  aAtom->~nsDynamicAtom();
111
0
  free(aAtom);
112
0
}
113
114
const nsStaticAtom*
115
nsAtom::AsStatic() const
116
198k
{
117
198k
  MOZ_ASSERT(IsStatic());
118
198k
  return static_cast<const nsStaticAtom*>(this);
119
198k
}
120
121
const nsDynamicAtom*
122
nsAtom::AsDynamic() const
123
5.77k
{
124
5.77k
  MOZ_ASSERT(IsDynamic());
125
5.77k
  return static_cast<const nsDynamicAtom*>(this);
126
5.77k
}
127
128
nsDynamicAtom*
129
nsAtom::AsDynamic()
130
11.5k
{
131
11.5k
  MOZ_ASSERT(IsDynamic());
132
11.5k
  return static_cast<nsDynamicAtom*>(this);
133
11.5k
}
134
135
void
136
nsAtom::ToString(nsAString& aString) const
137
0
{
138
0
  // See the comment on |mString|'s declaration.
139
0
  if (IsStatic()) {
140
0
    // AssignLiteral() lets us assign without copying. This isn't a string
141
0
    // literal, but it's a static atom and thus has an unbounded lifetime,
142
0
    // which is what's important.
143
0
    aString.AssignLiteral(AsStatic()->String(), mLength);
144
0
  } else {
145
0
    aString.Assign(AsDynamic()->String(), mLength);
146
0
  }
147
0
}
148
149
void
150
nsAtom::ToUTF8String(nsACString& aBuf) const
151
0
{
152
0
  MOZ_ASSERT(!IsDynamicHTML5(),
153
0
             "Called ToUTF8String() on a dynamic HTML5 atom");
154
0
  CopyUTF16toUTF8(nsDependentString(GetUTF16String(), mLength), aBuf);
155
0
}
156
157
void
158
nsAtom::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes)
159
  const
160
0
{
161
0
  MOZ_ASSERT(!IsDynamicHTML5(),
162
0
             "Called AddSizeOfIncludingThis() on a dynamic HTML5 atom");
163
0
164
0
  // Static atoms are in static memory, and so are not measured here.
165
0
  if (IsDynamic()) {
166
0
    aSizes.mDynamicAtoms += aMallocSizeOf(this);
167
0
  }
168
0
}
169
170
char16ptr_t
171
nsAtom::GetUTF16String() const
172
204k
{
173
204k
  return IsStatic() ? AsStatic()->String() : AsDynamic()->String();
174
204k
}
175
176
//----------------------------------------------------------------------
177
178
struct AtomTableKey
179
{
180
  explicit AtomTableKey(const nsStaticAtom* aAtom)
181
    : mUTF16String(aAtom->String())
182
    , mUTF8String(nullptr)
183
    , mLength(aAtom->GetLength())
184
    , mHash(aAtom->hash())
185
6.98k
  {
186
6.98k
    MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash);
187
6.98k
  }
188
189
  AtomTableKey(const char16_t* aUTF16String, uint32_t aLength,
190
               uint32_t* aHashOut)
191
    : mUTF16String(aUTF16String)
192
    , mUTF8String(nullptr)
193
    , mLength(aLength)
194
429
  {
195
429
    mHash = HashString(mUTF16String, mLength);
196
429
    *aHashOut = mHash;
197
429
  }
198
199
  AtomTableKey(const char* aUTF8String,
200
               uint32_t aLength,
201
               uint32_t* aHashOut,
202
               bool* aErr)
203
    : mUTF16String(nullptr)
204
    , mUTF8String(aUTF8String)
205
    , mLength(aLength)
206
11.7k
  {
207
11.7k
    mHash = HashUTF8AsUTF16(mUTF8String, mLength, aErr);
208
11.7k
    *aHashOut = mHash;
209
11.7k
  }
210
211
  const char16_t* mUTF16String;
212
  const char* mUTF8String;
213
  uint32_t mLength;
214
  uint32_t mHash;
215
};
216
217
struct AtomTableEntry : public PLDHashEntryHdr
218
{
219
  // These references are either to dynamic atoms, in which case they are
220
  // non-owning, or they are to static atoms, which aren't really refcounted.
221
  // See the comment at the top of this file for more details.
222
  nsAtom* MOZ_NON_OWNING_REF mAtom;
223
};
224
225
struct AtomCache : public MruCache<AtomTableKey, nsAtom*, AtomCache>
226
{
227
0
  static HashNumber Hash(const AtomTableKey& aKey) { return aKey.mHash; }
228
  static bool Match(const AtomTableKey& aKey, const nsAtom* aVal)
229
0
  {
230
0
    MOZ_ASSERT(aKey.mUTF16String);
231
0
    return aVal->Equals(aKey.mUTF16String, aKey.mLength);
232
0
  }
233
};
234
235
static AtomCache sRecentlyUsedMainThreadAtoms;
236
237
// In order to reduce locking contention for concurrent atomization, we segment
238
// the atom table into N subtables, each with a separate lock. If the hash
239
// values we use to select the subtable are evenly distributed, this reduces the
240
// probability of contention by a factor of N. See bug 1440824.
241
//
242
// NB: This is somewhat similar to the technique used by Java's
243
// ConcurrentHashTable.
244
class nsAtomSubTable
245
{
246
  friend class nsAtomTable;
247
  Mutex mLock;
248
  PLDHashTable mTable;
249
  nsAtomSubTable();
250
  void GCLocked(GCKind aKind);
251
  void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
252
                                    AtomsSizes& aSizes);
253
254
  AtomTableEntry* Search(AtomTableKey& aKey) const
255
411
  {
256
411
    mLock.AssertCurrentThreadOwns();
257
411
    return static_cast<AtomTableEntry*>(mTable.Search(&aKey));
258
411
  }
259
260
  AtomTableEntry* Add(AtomTableKey& aKey)
261
18.7k
  {
262
18.7k
    mLock.AssertCurrentThreadOwns();
263
18.7k
    return static_cast<AtomTableEntry*>(mTable.Add(&aKey)); // Infallible
264
18.7k
  }
265
};
266
267
// The outer atom table, which coordinates access to the inner array of
268
// subtables.
269
class nsAtomTable
270
{
271
public:
272
  nsAtomSubTable& SelectSubTable(AtomTableKey& aKey);
273
  void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes);
274
  void GC(GCKind aKind);
275
  already_AddRefed<nsAtom> Atomize(const nsAString& aUTF16String);
276
  already_AddRefed<nsAtom> Atomize(const nsACString& aUTF8String);
277
  already_AddRefed<nsAtom> AtomizeMainThread(const nsAString& aUTF16String);
278
  nsStaticAtom* GetStaticAtom(const nsAString& aUTF16String);
279
  void RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen);
280
281
  // The result of this function may be imprecise if other threads are operating
282
  // on atoms concurrently. It's also slow, since it triggers a GC before
283
  // counting.
284
  size_t RacySlowCount();
285
286
  // This hash table op is a static member of this class so that it can take
287
  // advantage of |friend| declarations.
288
  static void AtomTableClearEntry(PLDHashTable* aTable,
289
                                  PLDHashEntryHdr* aEntry);
290
291
  // We achieve measurable reduction in locking contention in parallel CSS
292
  // parsing by increasing the number of subtables up to 128. This has been
293
  // measured to have neglible impact on the performance of initialization, GC,
294
  // and shutdown.
295
  //
296
  // Another important consideration is memory, since we're adding fixed
297
  // overhead per content process, which we try to avoid. Measuring a
298
  // mostly-empty page [1] with various numbers of subtables, we get the
299
  // following deep sizes for the atom table:
300
  //       1 subtable:  278K
301
  //       8 subtables: 279K
302
  //      16 subtables: 282K
303
  //      64 subtables: 286K
304
  //     128 subtables: 290K
305
  //
306
  // So 128 subtables costs us 12K relative to a single table, and 4K relative
307
  // to 64 subtables. Conversely, measuring parallel (6 thread) CSS parsing on
308
  // tp6-facebook, a single table provides ~150ms of locking overhead per
309
  // thread, 64 subtables provides ~2-3ms of overhead, and 128 subtables
310
  // provides <1ms. And so while either 64 or 128 subtables would probably be
311
  // acceptable, achieving a measurable reduction in contention for 4k of fixed
312
  // memory overhead is probably worth it.
313
  //
314
  // [1] The numbers will look different for content processes with complex
315
  // pages loaded, but in those cases the actual atoms will dominate memory
316
  // usage and the overhead of extra tables will be negligible. We're mostly
317
  // interested in the fixed cost for nearly-empty content processes.
318
  const static size_t kNumSubTables = 128; // Must be power of two.
319
320
private:
321
  nsAtomSubTable mSubTables[kNumSubTables];
322
};
323
324
// Static singleton instance for the atom table.
325
static nsAtomTable* gAtomTable;
326
327
static PLDHashNumber
328
AtomTableGetHash(const void* aKey)
329
19.1k
{
330
19.1k
  const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
331
19.1k
  return k->mHash;
332
19.1k
}
333
334
static bool
335
AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey)
336
12.1k
{
337
12.1k
  const AtomTableEntry* he = static_cast<const AtomTableEntry*>(aEntry);
338
12.1k
  const AtomTableKey* k = static_cast<const AtomTableKey*>(aKey);
339
12.1k
340
12.1k
  if (k->mUTF8String) {
341
11.7k
    bool err = false;
342
11.7k
    return (CompareUTF8toUTF16(nsDependentCSubstring(
343
11.7k
                                 k->mUTF8String, k->mUTF8String + k->mLength),
344
11.7k
                               nsDependentAtomString(he->mAtom),
345
11.7k
                               &err) == 0) &&
346
11.7k
           !err;
347
11.7k
  }
348
411
349
411
  return he->mAtom->Equals(k->mUTF16String, k->mLength);
350
411
}
351
352
void
353
nsAtomTable::AtomTableClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry)
354
0
{
355
0
  auto entry = static_cast<AtomTableEntry*>(aEntry);
356
0
  entry->mAtom = nullptr;
357
0
}
358
359
static void
360
AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey)
361
7.01k
{
362
7.01k
  static_cast<AtomTableEntry*>(aEntry)->mAtom = nullptr;
363
7.01k
}
364
365
static const PLDHashTableOps AtomTableOps = {
366
  AtomTableGetHash,
367
  AtomTableMatchKey,
368
  PLDHashTable::MoveEntryStub,
369
  nsAtomTable::AtomTableClearEntry,
370
  AtomTableInitEntry
371
};
372
373
// The atom table very quickly gets 10,000+ entries in it (or even 100,000+).
374
// But choosing the best initial subtable length has some subtleties: we add
375
// ~2700 static atoms at start-up, and then we start adding and removing
376
// dynamic atoms. If we make the tables too big to start with, when the first
377
// dynamic atom gets removed from a given table the load factor will be < 25%
378
// and we will shrink it.
379
//
380
// So we first make the simplifying assumption that the atoms are more or less
381
// evenly-distributed across the subtables (which is the case empirically).
382
// Then, we take the total atom count when the first dynamic atom is removed
383
// (~2700), divide that across the N subtables, and the largest capacity that
384
// will allow each subtable to be > 25% full with that count.
385
//
386
// So want an initial subtable capacity less than (2700 / N) * 4 = 10800 / N.
387
// Rounding down to the nearest power of two gives us 8192 / N. Since the
388
// capacity is double the initial length, we end up with (4096 / N) per subtable.
389
#define INITIAL_SUBTABLE_LENGTH (4096 / nsAtomTable::kNumSubTables)
390
391
nsAtomSubTable&
392
nsAtomTable::SelectSubTable(AtomTableKey& aKey)
393
19.1k
{
394
19.1k
  // There are a few considerations around how we select subtables.
395
19.1k
  //
396
19.1k
  // First, we want entries to be evenly distributed across the subtables. This
397
19.1k
  // can be achieved by using any bits in the hash key, assuming the key itself
398
19.1k
  // is evenly-distributed. Empirical measurements indicate that this method
399
19.1k
  // produces a roughly-even distribution across subtables.
400
19.1k
  //
401
19.1k
  // Second, we want to use the hash bits that are least likely to influence an
402
19.1k
  // entry's position within the subtable. If we used the exact same bits used
403
19.1k
  // by the subtables, then each subtable would compute the same position for
404
19.1k
  // every entry it observes, leading to pessimal performance. In this case,
405
19.1k
  // we're using PLDHashTable, whose primary hash function uses the N leftmost
406
19.1k
  // bits of the hash value (where N is the log2 capacity of the table). This
407
19.1k
  // means we should prefer the rightmost bits here.
408
19.1k
  //
409
19.1k
  // Note that the below is equivalent to mHash % kNumSubTables, a replacement
410
19.1k
  // which an optimizing compiler should make, but let's avoid any doubt.
411
19.1k
  static_assert((kNumSubTables & (kNumSubTables - 1)) == 0, "must be power of two");
412
19.1k
  return mSubTables[aKey.mHash & (kNumSubTables - 1)];
413
19.1k
}
414
415
void
416
nsAtomTable::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
417
                                    AtomsSizes& aSizes)
418
0
{
419
0
  MOZ_ASSERT(NS_IsMainThread());
420
0
  aSizes.mTable += aMallocSizeOf(this);
421
0
  for (auto& table : mSubTables) {
422
0
    MutexAutoLock lock(table.mLock);
423
0
    table.AddSizeOfExcludingThisLocked(aMallocSizeOf, aSizes);
424
0
  }
425
0
}
426
427
void nsAtomTable::GC(GCKind aKind)
428
0
{
429
0
  MOZ_ASSERT(NS_IsMainThread());
430
0
  sRecentlyUsedMainThreadAtoms.Clear();
431
0
432
0
  // Note that this is effectively an incremental GC, since only one subtable
433
0
  // is locked at a time.
434
0
  for (auto& table: mSubTables) {
435
0
    MutexAutoLock lock(table.mLock);
436
0
    table.GCLocked(aKind);
437
0
  }
438
0
439
0
  // We would like to assert that gUnusedAtomCount matches the number of atoms
440
0
  // we found in the table which we removed. However, there are two problems
441
0
  // with this:
442
0
  // * We have multiple subtables, each with their own lock. For optimal
443
0
  //   performance we only want to hold one lock at a time, but this means
444
0
  //   that atoms can be added and removed between GC slices.
445
0
  // * Even if we held all the locks and performed all GC slices atomically,
446
0
  //   the locks are not acquired for AddRef() and Release() calls. This means
447
0
  //   we might see a gUnusedAtomCount value in between, say, AddRef()
448
0
  //   incrementing mRefCnt and it decrementing gUnusedAtomCount.
449
0
  //
450
0
  // So, we don't bother asserting that there are no unused atoms at the end of
451
0
  // a regular GC. But we can (and do) assert this just after the last GC at
452
0
  // shutdown.
453
0
  //
454
0
  // Note that, barring refcounting bugs, an atom can only go from a zero
455
0
  // refcount to a non-zero refcount while the atom table lock is held, so
456
0
  // so we won't try to resurrect a zero refcount atom while trying to delete
457
0
  // it.
458
0
459
0
  MOZ_ASSERT_IF(aKind == GCKind::Shutdown, gUnusedAtomCount == 0);
460
0
}
461
462
size_t
463
nsAtomTable::RacySlowCount()
464
0
{
465
0
  // Trigger a GC so that the result is deterministic modulo other threads.
466
0
  GC(GCKind::RegularOperation);
467
0
  size_t count = 0;
468
0
  for (auto& table: mSubTables) {
469
0
    MutexAutoLock lock(table.mLock);
470
0
    count += table.mTable.EntryCount();
471
0
  }
472
0
473
0
  return count;
474
0
}
475
476
nsAtomSubTable::nsAtomSubTable()
477
  : mLock("Atom Sub-Table Lock")
478
  , mTable(&AtomTableOps, sizeof(AtomTableEntry), INITIAL_SUBTABLE_LENGTH)
479
384
{
480
384
}
481
482
void
483
nsAtomSubTable::GCLocked(GCKind aKind)
484
0
{
485
0
  MOZ_ASSERT(NS_IsMainThread());
486
0
  mLock.AssertCurrentThreadOwns();
487
0
488
0
  int32_t removedCount = 0; // A non-atomic temporary for cheaper increments.
489
0
  nsAutoCString nonZeroRefcountAtoms;
490
0
  uint32_t nonZeroRefcountAtomsCount = 0;
491
0
  for (auto i = mTable.Iter(); !i.Done(); i.Next()) {
492
0
    auto entry = static_cast<AtomTableEntry*>(i.Get());
493
0
    if (entry->mAtom->IsStatic()) {
494
0
      continue;
495
0
    }
496
0
497
0
    nsAtom* atom = entry->mAtom;
498
0
    MOZ_ASSERT(!atom->IsDynamicHTML5());
499
0
    if (atom->IsDynamic() && atom->AsDynamic()->mRefCnt == 0) {
500
0
      i.Remove();
501
0
      nsDynamicAtom::Destroy(atom->AsDynamic());
502
0
      ++removedCount;
503
0
    }
504
#ifdef NS_FREE_PERMANENT_DATA
505
    else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) {
506
      // Only report leaking atoms in leak-checking builds in a run where we
507
      // are checking for leaks, during shutdown. If something is anomalous,
508
      // then we'll assert later in this function.
509
      nsAutoCString name;
510
      atom->ToUTF8String(name);
511
      if (nonZeroRefcountAtomsCount == 0) {
512
        nonZeroRefcountAtoms = name;
513
      } else if (nonZeroRefcountAtomsCount < 20) {
514
        nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",") + name;
515
      } else if (nonZeroRefcountAtomsCount == 20) {
516
        nonZeroRefcountAtoms += NS_LITERAL_CSTRING(",...");
517
      }
518
      nonZeroRefcountAtomsCount++;
519
    }
520
#endif
521
522
0
  }
523
0
  if (nonZeroRefcountAtomsCount) {
524
0
    nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s",
525
0
                        nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get());
526
0
    NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get());
527
0
  }
528
0
529
0
  gUnusedAtomCount -= removedCount;
530
0
}
531
532
static void
533
GCAtomTable()
534
0
{
535
0
  MOZ_ASSERT(gAtomTable);
536
0
  if (NS_IsMainThread()) {
537
0
    gAtomTable->GC(GCKind::RegularOperation);
538
0
  }
539
0
}
540
541
MOZ_ALWAYS_INLINE MozExternalRefCountType
542
nsDynamicAtom::AddRef()
543
5.79k
{
544
5.79k
  MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
545
5.79k
  nsrefcnt count = ++mRefCnt;
546
5.79k
  if (count == 1) {
547
5.77k
    gUnusedAtomCount--;
548
5.77k
  }
549
5.79k
  return count;
550
5.79k
}
551
552
MOZ_ALWAYS_INLINE MozExternalRefCountType
553
nsDynamicAtom::Release()
554
5.78k
{
555
  #ifdef DEBUG
556
  // We set a lower GC threshold for atoms in debug builds so that we exercise
557
  // the GC machinery more often.
558
  static const int32_t kAtomGCThreshold = 20;
559
  #else
560
  static const int32_t kAtomGCThreshold = 10000;
561
5.78k
  #endif
562
5.78k
563
5.78k
  MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
564
5.78k
  nsrefcnt count = --mRefCnt;
565
5.78k
  if (count == 0) {
566
5.78k
    if (++gUnusedAtomCount >= kAtomGCThreshold) {
567
0
      GCAtomTable();
568
0
    }
569
5.78k
  }
570
5.78k
571
5.78k
  return count;
572
5.78k
}
573
574
MozExternalRefCountType
575
nsAtom::AddRef()
576
12.6k
{
577
12.6k
  MOZ_ASSERT(!IsDynamicHTML5(), "Attempt to AddRef a dynamic HTML5 atom");
578
12.6k
579
12.6k
  return IsStatic() ? 2 : AsDynamic()->AddRef();
580
12.6k
}
581
582
MozExternalRefCountType
583
nsAtom::Release()
584
11.8k
{
585
11.8k
  MOZ_ASSERT(!IsDynamicHTML5(), "Attempt to Release a dynamic HTML5 atom");
586
11.8k
587
11.8k
  return IsStatic() ? 1 : AsDynamic()->Release();
588
11.8k
}
589
590
//----------------------------------------------------------------------
591
592
// Have the static atoms been inserted into the table?
593
static bool gStaticAtomsDone = false;
594
595
void
596
NS_InitAtomTable()
597
3
{
598
3
  MOZ_ASSERT(!gAtomTable);
599
3
  gAtomTable = new nsAtomTable();
600
3
601
3
  // Bug 1340710 has caused us to use an empty atom at arbitrary times after
602
3
  // startup. If we end up creating one before nsGkAtoms::_empty is registered,
603
3
  // we get an assertion about transmuting a dynamic atom into a static atom.
604
3
  // In order to avoid that, we register nsGkAtoms immediately after creating
605
3
  // the atom table to guarantee that the empty string atom will always be
606
3
  // static.
607
3
  nsGkAtoms::RegisterStaticAtoms();
608
3
}
609
610
void
611
NS_ShutdownAtomTable()
612
0
{
613
0
  MOZ_ASSERT(NS_IsMainThread());
614
0
  MOZ_ASSERT(gAtomTable);
615
0
616
#ifdef NS_FREE_PERMANENT_DATA
617
  // Do a final GC to satisfy leak checking. We skip this step in release
618
  // builds.
619
  gAtomTable->GC(GCKind::Shutdown);
620
#endif
621
622
0
  delete gAtomTable;
623
0
  gAtomTable = nullptr;
624
0
}
625
626
void
627
NS_AddSizeOfAtoms(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes)
628
0
{
629
0
  MOZ_ASSERT(NS_IsMainThread());
630
0
  MOZ_ASSERT(gAtomTable);
631
0
  return gAtomTable->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
632
0
}
633
634
void
635
nsAtomSubTable::AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf,
636
                                             AtomsSizes& aSizes)
637
0
{
638
0
  mLock.AssertCurrentThreadOwns();
639
0
  aSizes.mTable += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf);
640
0
  for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) {
641
0
    auto entry = static_cast<AtomTableEntry*>(iter.Get());
642
0
    entry->mAtom->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
643
0
  }
644
0
}
645
646
void
647
nsAtomTable::RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen)
648
3
{
649
3
  MOZ_ASSERT(NS_IsMainThread());
650
3
  MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!");
651
3
652
6.98k
  for (uint32_t i = 0; i < aAtomsLen; ++i) {
653
6.98k
    const nsStaticAtom* atom = &aAtoms[i];
654
6.98k
    MOZ_ASSERT(nsCRT::IsAscii(atom->String()));
655
6.98k
    MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength());
656
6.98k
657
6.98k
    // This assertion ensures the static atom's precomputed hash value matches
658
6.98k
    // what would be computed by mozilla::HashString(aStr), which is what we use
659
6.98k
    // when atomizing strings. We compute this hash in Atom.py.
660
6.98k
    MOZ_ASSERT(HashString(atom->String()) == atom->hash());
661
6.98k
662
6.98k
    AtomTableKey key(atom);
663
6.98k
    nsAtomSubTable& table = SelectSubTable(key);
664
6.98k
    MutexAutoLock lock(table.mLock);
665
6.98k
    AtomTableEntry* he = table.Add(key);
666
6.98k
667
6.98k
    if (he->mAtom) {
668
0
      // There are two ways we could get here.
669
0
      // - Register two static atoms with the same string.
670
0
      // - Create a dynamic atom and then register a static atom with the same
671
0
      //   string while the dynamic atom is alive.
672
0
      // Both cases can cause subtle bugs, and are disallowed. We're
673
0
      // programming in C++ here, not Smalltalk.
674
0
      nsAutoCString name;
675
0
      he->mAtom->ToUTF8String(name);
676
0
      MOZ_CRASH_UNSAFE_PRINTF("Atom for '%s' already exists", name.get());
677
0
    }
678
6.98k
    he->mAtom = const_cast<nsStaticAtom*>(atom);
679
6.98k
  }
680
3
}
681
682
void
683
NS_RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen)
684
3
{
685
3
  MOZ_ASSERT(NS_IsMainThread());
686
3
  MOZ_ASSERT(gAtomTable);
687
3
  gAtomTable->RegisterStaticAtoms(aAtoms, aAtomsLen);
688
3
  gStaticAtomsDone = true;
689
3
}
690
691
already_AddRefed<nsAtom>
692
NS_Atomize(const char* aUTF8String)
693
153
{
694
153
  MOZ_ASSERT(gAtomTable);
695
153
  return gAtomTable->Atomize(nsDependentCString(aUTF8String));
696
153
}
697
698
already_AddRefed<nsAtom>
699
nsAtomTable::Atomize(const nsACString& aUTF8String)
700
11.7k
{
701
11.7k
  uint32_t hash;
702
11.7k
  bool err;
703
11.7k
  AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &hash, &err);
704
11.7k
  if (MOZ_UNLIKELY(err)) {
705
0
    MOZ_ASSERT_UNREACHABLE("Tried to atomize invalid UTF-8.");
706
0
    // The input was invalid UTF-8. Let's replace the errors with U+FFFD
707
0
    // and atomize the result.
708
0
    nsString str;
709
0
    CopyUTF8toUTF16(aUTF8String, str);
710
0
    return Atomize(str);
711
0
  }
712
11.7k
  nsAtomSubTable& table = SelectSubTable(key);
713
11.7k
  MutexAutoLock lock(table.mLock);
714
11.7k
  AtomTableEntry* he = table.Add(key);
715
11.7k
716
11.7k
  if (he->mAtom) {
717
11.7k
    RefPtr<nsAtom> atom = he->mAtom;
718
11.7k
719
11.7k
    return atom.forget();
720
11.7k
  }
721
19
722
19
  nsString str;
723
19
  CopyUTF8toUTF16(aUTF8String, str);
724
19
  RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(str, hash));
725
19
726
19
  he->mAtom = atom;
727
19
728
19
  return atom.forget();
729
19
}
730
731
already_AddRefed<nsAtom>
732
NS_Atomize(const nsACString& aUTF8String)
733
11.5k
{
734
11.5k
  MOZ_ASSERT(gAtomTable);
735
11.5k
  return gAtomTable->Atomize(aUTF8String);
736
11.5k
}
737
738
already_AddRefed<nsAtom>
739
NS_Atomize(const char16_t* aUTF16String)
740
0
{
741
0
  MOZ_ASSERT(gAtomTable);
742
0
  return gAtomTable->Atomize(nsDependentString(aUTF16String));
743
0
}
744
745
already_AddRefed<nsAtom>
746
nsAtomTable::Atomize(const nsAString& aUTF16String)
747
18
{
748
18
  uint32_t hash;
749
18
  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
750
18
  nsAtomSubTable& table = SelectSubTable(key);
751
18
  MutexAutoLock lock(table.mLock);
752
18
  AtomTableEntry* he = table.Add(key);
753
18
754
18
  if (he->mAtom) {
755
0
    RefPtr<nsAtom> atom = he->mAtom;
756
0
757
0
    return atom.forget();
758
0
  }
759
18
760
18
  RefPtr<nsAtom> atom = dont_AddRef(nsDynamicAtom::Create(aUTF16String, hash));
761
18
  he->mAtom = atom;
762
18
763
18
  return atom.forget();
764
18
}
765
766
already_AddRefed<nsAtom>
767
NS_Atomize(const nsAString& aUTF16String)
768
18
{
769
18
  MOZ_ASSERT(gAtomTable);
770
18
  return gAtomTable->Atomize(aUTF16String);
771
18
}
772
773
already_AddRefed<nsAtom>
774
nsAtomTable::AtomizeMainThread(const nsAString& aUTF16String)
775
0
{
776
0
  MOZ_ASSERT(NS_IsMainThread());
777
0
  RefPtr<nsAtom> retVal;
778
0
  uint32_t hash;
779
0
  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
780
0
  auto p = sRecentlyUsedMainThreadAtoms.Lookup(key);
781
0
  if (p) {
782
0
    retVal = p.Data();
783
0
    return retVal.forget();
784
0
  }
785
0
786
0
  nsAtomSubTable& table = SelectSubTable(key);
787
0
  MutexAutoLock lock(table.mLock);
788
0
  AtomTableEntry* he = table.Add(key);
789
0
790
0
  if (he->mAtom) {
791
0
    retVal = he->mAtom;
792
0
  } else {
793
0
    RefPtr<nsAtom> newAtom =
794
0
      dont_AddRef(nsDynamicAtom::Create(aUTF16String, hash));
795
0
    he->mAtom = newAtom;
796
0
    retVal = newAtom.forget();
797
0
  }
798
0
799
0
  p.Set(retVal);
800
0
  return retVal.forget();
801
0
}
802
803
already_AddRefed<nsAtom>
804
NS_AtomizeMainThread(const nsAString& aUTF16String)
805
0
{
806
0
  MOZ_ASSERT(gAtomTable);
807
0
  return gAtomTable->AtomizeMainThread(aUTF16String);
808
0
}
809
810
nsrefcnt
811
NS_GetNumberOfAtoms(void)
812
0
{
813
0
  MOZ_ASSERT(gAtomTable);
814
0
  return gAtomTable->RacySlowCount();
815
0
}
816
817
int32_t
818
NS_GetUnusedAtomCount(void)
819
0
{
820
0
  return gUnusedAtomCount;
821
0
}
822
823
nsStaticAtom*
824
NS_GetStaticAtom(const nsAString& aUTF16String)
825
411
{
826
411
  MOZ_ASSERT(gStaticAtomsDone, "Static atom setup not yet done.");
827
411
  MOZ_ASSERT(gAtomTable);
828
411
  return gAtomTable->GetStaticAtom(aUTF16String);
829
411
}
830
831
nsStaticAtom*
832
nsAtomTable::GetStaticAtom(const nsAString& aUTF16String)
833
411
{
834
411
  uint32_t hash;
835
411
  AtomTableKey key(aUTF16String.Data(), aUTF16String.Length(), &hash);
836
411
  nsAtomSubTable& table = SelectSubTable(key);
837
411
  MutexAutoLock lock(table.mLock);
838
411
  AtomTableEntry* he = table.Search(key);
839
411
  return he && he->mAtom->IsStatic()
840
411
       ? static_cast<nsStaticAtom*>(he->mAtom)
841
411
       : nullptr;
842
411
}
843
844
void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom)
845
0
{
846
0
  // Assume the common case is that the atom is already ASCII lowercase.
847
0
  bool reAtomize = false;
848
0
  const nsDependentString existing(aAtom->GetUTF16String(), aAtom->GetLength());
849
0
  for (size_t i = 0; i < existing.Length(); ++i) {
850
0
    if (IS_ASCII_UPPER(existing[i])) {
851
0
      reAtomize = true;
852
0
      break;
853
0
    }
854
0
  }
855
0
856
0
  // If the string was already lowercase, we're done.
857
0
  if (!reAtomize) {
858
0
    return;
859
0
  }
860
0
861
0
  nsAutoString lowercased;
862
0
  ToLowerCaseASCII(existing, lowercased);
863
0
  aAtom = NS_Atomize(lowercased);
864
0
}