/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 | } |