|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* vim: set ts=8 sts=4 et sw=4 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 // |
|
8 // This file implements a garbage-cycle collector based on the paper |
|
9 // |
|
10 // Concurrent Cycle Collection in Reference Counted Systems |
|
11 // Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072 |
|
12 // |
|
13 // We are not using the concurrent or acyclic cases of that paper; so |
|
14 // the green, red and orange colors are not used. |
|
15 // |
|
16 // The collector is based on tracking pointers of four colors: |
|
17 // |
|
18 // Black nodes are definitely live. If we ever determine a node is |
|
19 // black, it's ok to forget about, drop from our records. |
|
20 // |
|
21 // White nodes are definitely garbage cycles. Once we finish with our |
|
22 // scanning, we unlink all the white nodes and expect that by |
|
23 // unlinking them they will self-destruct (since a garbage cycle is |
|
24 // only keeping itself alive with internal links, by definition). |
|
25 // |
|
26 // Snow-white is an addition to the original algorithm. Snow-white object |
|
27 // has reference count zero and is just waiting for deletion. |
|
28 // |
|
29 // Grey nodes are being scanned. Nodes that turn grey will turn |
|
30 // either black if we determine that they're live, or white if we |
|
31 // determine that they're a garbage cycle. After the main collection |
|
32 // algorithm there should be no grey nodes. |
|
33 // |
|
34 // Purple nodes are *candidates* for being scanned. They are nodes we |
|
35 // haven't begun scanning yet because they're not old enough, or we're |
|
36 // still partway through the algorithm. |
|
37 // |
|
38 // XPCOM objects participating in garbage-cycle collection are obliged |
|
39 // to inform us when they ought to turn purple; that is, when their |
|
40 // refcount transitions from N+1 -> N, for nonzero N. Furthermore we |
|
41 // require that *after* an XPCOM object has informed us of turning |
|
42 // purple, they will tell us when they either transition back to being |
|
43 // black (incremented refcount) or are ultimately deleted. |
|
44 |
|
45 // Incremental cycle collection |
|
46 // |
|
47 // Beyond the simple state machine required to implement incremental |
|
48 // collection, the CC needs to be able to compensate for things the browser |
|
49 // is doing during the collection. There are two kinds of problems. For each |
|
50 // of these, there are two cases to deal with: purple-buffered C++ objects |
|
51 // and JS objects. |
|
52 |
|
53 // The first problem is that an object in the CC's graph can become garbage. |
|
54 // This is bad because the CC touches the objects in its graph at every |
|
55 // stage of its operation. |
|
56 // |
|
57 // All cycle collected C++ objects that die during a cycle collection |
|
58 // will end up actually getting deleted by the SnowWhiteKiller. Before |
|
59 // the SWK deletes an object, it checks if an ICC is running, and if so, |
|
60 // if the object is in the graph. If it is, the CC clears mPointer and |
|
61 // mParticipant so it does not point to the raw object any more. Because |
|
62 // objects could die any time the CC returns to the mutator, any time the CC |
|
63 // accesses a PtrInfo it must perform a null check on mParticipant to |
|
64 // ensure the object has not gone away. |
|
65 // |
|
66 // JS objects don't always run finalizers, so the CC can't remove them from |
|
67 // the graph when they die. Fortunately, JS objects can only die during a GC, |
|
68 // so if a GC is begun during an ICC, the browser synchronously finishes off |
|
69 // the ICC, which clears the entire CC graph. If the GC and CC are scheduled |
|
70 // properly, this should be rare. |
|
71 // |
|
72 // The second problem is that objects in the graph can be changed, say by |
|
73 // being addrefed or released, or by having a field updated, after the object |
|
74 // has been added to the graph. The problem is that ICC can miss a newly |
|
75 // created reference to an object, and end up unlinking an object that is |
|
76 // actually alive. |
|
77 // |
|
78 // The basic idea of the solution, from "An on-the-fly Reference Counting |
|
79 // Garbage Collector for Java" by Levanoni and Petrank, is to notice if an |
|
80 // object has had an additional reference to it created during the collection, |
|
81 // and if so, don't collect it during the current collection. This avoids having |
|
82 // to rerun the scan as in Bacon & Rajan 2001. |
|
83 // |
|
84 // For cycle collected C++ objects, we modify AddRef to place the object in |
|
85 // the purple buffer, in addition to Release. Then, in the CC, we treat any |
|
86 // objects in the purple buffer as being alive, after graph building has |
|
87 // completed. Because they are in the purple buffer, they will be suspected |
|
88 // in the next CC, so there's no danger of leaks. This is imprecise, because |
|
89 // we will treat as live an object that has been Released but not AddRefed |
|
90 // during graph building, but that's probably rare enough that the additional |
|
91 // bookkeeping overhead is not worthwhile. |
|
92 // |
|
93 // For JS objects, the cycle collector is only looking at gray objects. If a |
|
94 // gray object is touched during ICC, it will be made black by UnmarkGray. |
|
95 // Thus, if a JS object has become black during the ICC, we treat it as live. |
|
96 // Merged JS zones have to be handled specially: we scan all zone globals. |
|
97 // If any are black, we treat the zone as being black. |
|
98 |
|
99 |
|
100 // Safety |
|
101 // |
|
102 // An XPCOM object is either scan-safe or scan-unsafe, purple-safe or |
|
103 // purple-unsafe. |
|
104 // |
|
105 // An nsISupports object is scan-safe if: |
|
106 // |
|
107 // - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though |
|
108 // this operation loses ISupports identity (like nsIClassInfo). |
|
109 // - Additionally, the operation |traverse| on the resulting |
|
110 // nsXPCOMCycleCollectionParticipant does not cause *any* refcount |
|
111 // adjustment to occur (no AddRef / Release calls). |
|
112 // |
|
113 // A non-nsISupports ("native") object is scan-safe by explicitly |
|
114 // providing its nsCycleCollectionParticipant. |
|
115 // |
|
116 // An object is purple-safe if it satisfies the following properties: |
|
117 // |
|
118 // - The object is scan-safe. |
|
119 // |
|
120 // When we receive a pointer |ptr| via |
|
121 // |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We |
|
122 // can check the scan-safety, but have no way to ensure the |
|
123 // purple-safety; objects must obey, or else the entire system falls |
|
124 // apart. Don't involve an object in this scheme if you can't |
|
125 // guarantee its purple-safety. The easiest way to ensure that an |
|
126 // object is purple-safe is to use nsCycleCollectingAutoRefCnt. |
|
127 // |
|
128 // When we have a scannable set of purple nodes ready, we begin |
|
129 // our walks. During the walks, the nodes we |traverse| should only |
|
130 // feed us more scan-safe nodes, and should not adjust the refcounts |
|
131 // of those nodes. |
|
132 // |
|
133 // We do not |AddRef| or |Release| any objects during scanning. We |
|
134 // rely on the purple-safety of the roots that call |suspect| to |
|
135 // hold, such that we will clear the pointer from the purple buffer |
|
136 // entry to the object before it is destroyed. The pointers that are |
|
137 // merely scan-safe we hold only for the duration of scanning, and |
|
138 // there should be no objects released from the scan-safe set during |
|
139 // the scan. |
|
140 // |
|
141 // We *do* call |Root| and |Unroot| on every white object, on |
|
142 // either side of the calls to |Unlink|. This keeps the set of white |
|
143 // objects alive during the unlinking. |
|
144 // |
|
145 |
|
146 #if !defined(__MINGW32__) |
|
147 #ifdef WIN32 |
|
148 #include <crtdbg.h> |
|
149 #include <errno.h> |
|
150 #endif |
|
151 #endif |
|
152 |
|
153 #include "base/process_util.h" |
|
154 |
|
155 #include "mozilla/ArrayUtils.h" |
|
156 #include "mozilla/AutoRestore.h" |
|
157 #include "mozilla/CycleCollectedJSRuntime.h" |
|
158 #include "mozilla/HoldDropJSObjects.h" |
|
159 /* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ |
|
160 #include "mozilla/MemoryReporting.h" |
|
161 #include "mozilla/LinkedList.h" |
|
162 |
|
163 #include "nsCycleCollectionParticipant.h" |
|
164 #include "nsCycleCollectionNoteRootCallback.h" |
|
165 #include "nsDeque.h" |
|
166 #include "nsCycleCollector.h" |
|
167 #include "nsThreadUtils.h" |
|
168 #include "nsXULAppAPI.h" |
|
169 #include "prenv.h" |
|
170 #include "nsPrintfCString.h" |
|
171 #include "nsTArray.h" |
|
172 #include "nsIConsoleService.h" |
|
173 #include "mozilla/Attributes.h" |
|
174 #include "nsICycleCollectorListener.h" |
|
175 #include "nsIMemoryReporter.h" |
|
176 #include "nsIFile.h" |
|
177 #include "nsDumpUtils.h" |
|
178 #include "xpcpublic.h" |
|
179 #include "GeckoProfiler.h" |
|
180 #include "js/SliceBudget.h" |
|
181 #include <stdint.h> |
|
182 #include <stdio.h> |
|
183 |
|
184 #include "mozilla/Likely.h" |
|
185 #include "mozilla/PoisonIOInterposer.h" |
|
186 #include "mozilla/Telemetry.h" |
|
187 #include "mozilla/ThreadLocal.h" |
|
188 |
|
189 using namespace mozilla; |
|
190 |
|
191 //#define COLLECT_TIME_DEBUG |
|
192 |
|
193 // Enable assertions that are useful for diagnosing errors in graph construction. |
|
194 //#define DEBUG_CC_GRAPH |
|
195 |
|
196 #define DEFAULT_SHUTDOWN_COLLECTIONS 5 |
|
197 |
|
198 // One to do the freeing, then another to detect there is no more work to do. |
|
199 #define NORMAL_SHUTDOWN_COLLECTIONS 2 |
|
200 |
|
201 // Cycle collector environment variables |
|
202 // |
|
203 // MOZ_CC_LOG_ALL: If defined, always log cycle collector heaps. |
|
204 // |
|
205 // MOZ_CC_LOG_SHUTDOWN: If defined, log cycle collector heaps at shutdown. |
|
206 // |
|
207 // MOZ_CC_LOG_THREAD: If set to "main", only automatically log main thread |
|
208 // CCs. If set to "worker", only automatically log worker CCs. If set to "all", |
|
209 // log either. The default value is "all". This must be used with either |
|
210 // MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything. |
|
211 // |
|
212 // MOZ_CC_LOG_PROCESS: If set to "main", only automatically log main process |
|
213 // CCs. If set to "content", only automatically log tab CCs. If set to |
|
214 // "plugins", only automatically log plugin CCs. If set to "all", log |
|
215 // everything. The default value is "all". This must be used with either |
|
216 // MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything. |
|
217 // |
|
218 // MOZ_CC_ALL_TRACES: If set to "all", any cycle collector |
|
219 // logging done will be WantAllTraces, which disables |
|
220 // various cycle collector optimizations to give a fuller picture of |
|
221 // the heap. If set to "shutdown", only shutdown logging will be WantAllTraces. |
|
222 // The default is none. |
|
223 // |
|
224 // MOZ_CC_RUN_DURING_SHUTDOWN: In non-DEBUG or builds, if this is set, |
|
225 // run cycle collections at shutdown. |
|
226 // |
|
227 // MOZ_CC_LOG_DIRECTORY: The directory in which logs are placed (such as |
|
228 // logs from MOZ_CC_LOG_ALL and MOZ_CC_LOG_SHUTDOWN, or other uses |
|
229 // of nsICycleCollectorListener) |
|
230 |
|
231 // Various parameters of this collector can be tuned using environment |
|
232 // variables. |
|
233 |
|
234 struct nsCycleCollectorParams |
|
235 { |
|
236 bool mLogAll; |
|
237 bool mLogShutdown; |
|
238 bool mAllTracesAll; |
|
239 bool mAllTracesShutdown; |
|
240 bool mLogThisThread; |
|
241 |
|
242 nsCycleCollectorParams() : |
|
243 mLogAll (PR_GetEnv("MOZ_CC_LOG_ALL") != nullptr), |
|
244 mLogShutdown (PR_GetEnv("MOZ_CC_LOG_SHUTDOWN") != nullptr), |
|
245 mAllTracesAll(false), |
|
246 mAllTracesShutdown(false) |
|
247 { |
|
248 const char* logThreadEnv = PR_GetEnv("MOZ_CC_LOG_THREAD"); |
|
249 bool threadLogging = true; |
|
250 if (logThreadEnv && !!strcmp(logThreadEnv, "all")) { |
|
251 if (NS_IsMainThread()) { |
|
252 threadLogging = !strcmp(logThreadEnv, "main"); |
|
253 } else { |
|
254 threadLogging = !strcmp(logThreadEnv, "worker"); |
|
255 } |
|
256 } |
|
257 |
|
258 const char* logProcessEnv = PR_GetEnv("MOZ_CC_LOG_PROCESS"); |
|
259 bool processLogging = true; |
|
260 if (logProcessEnv && !!strcmp(logProcessEnv, "all")) { |
|
261 switch (XRE_GetProcessType()) { |
|
262 case GeckoProcessType_Default: |
|
263 processLogging = !strcmp(logProcessEnv, "main"); |
|
264 break; |
|
265 case GeckoProcessType_Plugin: |
|
266 processLogging = !strcmp(logProcessEnv, "plugins"); |
|
267 break; |
|
268 case GeckoProcessType_Content: |
|
269 processLogging = !strcmp(logProcessEnv, "content"); |
|
270 break; |
|
271 default: |
|
272 processLogging = false; |
|
273 break; |
|
274 } |
|
275 } |
|
276 mLogThisThread = threadLogging && processLogging; |
|
277 |
|
278 const char* allTracesEnv = PR_GetEnv("MOZ_CC_ALL_TRACES"); |
|
279 if (allTracesEnv) { |
|
280 if (!strcmp(allTracesEnv, "all")) { |
|
281 mAllTracesAll = true; |
|
282 } else if (!strcmp(allTracesEnv, "shutdown")) { |
|
283 mAllTracesShutdown = true; |
|
284 } |
|
285 } |
|
286 } |
|
287 |
|
288 bool LogThisCC(bool aIsShutdown) |
|
289 { |
|
290 return (mLogAll || (aIsShutdown && mLogShutdown)) && mLogThisThread; |
|
291 } |
|
292 |
|
293 bool AllTracesThisCC(bool aIsShutdown) |
|
294 { |
|
295 return mAllTracesAll || (aIsShutdown && mAllTracesShutdown); |
|
296 } |
|
297 }; |
|
298 |
|
299 #ifdef COLLECT_TIME_DEBUG |
|
300 class TimeLog |
|
301 { |
|
302 public: |
|
303 TimeLog() : mLastCheckpoint(TimeStamp::Now()) {} |
|
304 |
|
305 void |
|
306 Checkpoint(const char* aEvent) |
|
307 { |
|
308 TimeStamp now = TimeStamp::Now(); |
|
309 uint32_t dur = (uint32_t) ((now - mLastCheckpoint).ToMilliseconds()); |
|
310 if (dur > 0) { |
|
311 printf("cc: %s took %dms\n", aEvent, dur); |
|
312 } |
|
313 mLastCheckpoint = now; |
|
314 } |
|
315 |
|
316 private: |
|
317 TimeStamp mLastCheckpoint; |
|
318 }; |
|
319 #else |
|
320 class TimeLog |
|
321 { |
|
322 public: |
|
323 TimeLog() {} |
|
324 void Checkpoint(const char* aEvent) {} |
|
325 }; |
|
326 #endif |
|
327 |
|
328 |
|
329 //////////////////////////////////////////////////////////////////////// |
|
330 // Base types |
|
331 //////////////////////////////////////////////////////////////////////// |
|
332 |
|
333 struct PtrInfo; |
|
334 |
|
335 class EdgePool |
|
336 { |
|
337 public: |
|
338 // EdgePool allocates arrays of void*, primarily to hold PtrInfo*. |
|
339 // However, at the end of a block, the last two pointers are a null |
|
340 // and then a void** pointing to the next block. This allows |
|
341 // EdgePool::Iterators to be a single word but still capable of crossing |
|
342 // block boundaries. |
|
343 |
|
344 EdgePool() |
|
345 { |
|
346 mSentinelAndBlocks[0].block = nullptr; |
|
347 mSentinelAndBlocks[1].block = nullptr; |
|
348 } |
|
349 |
|
350 ~EdgePool() |
|
351 { |
|
352 MOZ_ASSERT(!mSentinelAndBlocks[0].block && |
|
353 !mSentinelAndBlocks[1].block, |
|
354 "Didn't call Clear()?"); |
|
355 } |
|
356 |
|
357 void Clear() |
|
358 { |
|
359 Block *b = Blocks(); |
|
360 while (b) { |
|
361 Block *next = b->Next(); |
|
362 delete b; |
|
363 b = next; |
|
364 } |
|
365 |
|
366 mSentinelAndBlocks[0].block = nullptr; |
|
367 mSentinelAndBlocks[1].block = nullptr; |
|
368 } |
|
369 |
|
370 #ifdef DEBUG |
|
371 bool IsEmpty() |
|
372 { |
|
373 return !mSentinelAndBlocks[0].block && |
|
374 !mSentinelAndBlocks[1].block; |
|
375 } |
|
376 #endif |
|
377 |
|
378 private: |
|
379 struct Block; |
|
380 union PtrInfoOrBlock { |
|
381 // Use a union to avoid reinterpret_cast and the ensuing |
|
382 // potential aliasing bugs. |
|
383 PtrInfo *ptrInfo; |
|
384 Block *block; |
|
385 }; |
|
386 struct Block { |
|
387 enum { BlockSize = 16 * 1024 }; |
|
388 |
|
389 PtrInfoOrBlock mPointers[BlockSize]; |
|
390 Block() { |
|
391 mPointers[BlockSize - 2].block = nullptr; // sentinel |
|
392 mPointers[BlockSize - 1].block = nullptr; // next block pointer |
|
393 } |
|
394 Block*& Next() { return mPointers[BlockSize - 1].block; } |
|
395 PtrInfoOrBlock* Start() { return &mPointers[0]; } |
|
396 PtrInfoOrBlock* End() { return &mPointers[BlockSize - 2]; } |
|
397 }; |
|
398 |
|
399 // Store the null sentinel so that we can have valid iterators |
|
400 // before adding any edges and without adding any blocks. |
|
401 PtrInfoOrBlock mSentinelAndBlocks[2]; |
|
402 |
|
403 Block*& Blocks() { return mSentinelAndBlocks[1].block; } |
|
404 Block* Blocks() const { return mSentinelAndBlocks[1].block; } |
|
405 |
|
406 public: |
|
407 class Iterator |
|
408 { |
|
409 public: |
|
410 Iterator() : mPointer(nullptr) {} |
|
411 Iterator(PtrInfoOrBlock *aPointer) : mPointer(aPointer) {} |
|
412 Iterator(const Iterator& aOther) : mPointer(aOther.mPointer) {} |
|
413 |
|
414 Iterator& operator++() |
|
415 { |
|
416 if (mPointer->ptrInfo == nullptr) { |
|
417 // Null pointer is a sentinel for link to the next block. |
|
418 mPointer = (mPointer + 1)->block->mPointers; |
|
419 } |
|
420 ++mPointer; |
|
421 return *this; |
|
422 } |
|
423 |
|
424 PtrInfo* operator*() const |
|
425 { |
|
426 if (mPointer->ptrInfo == nullptr) { |
|
427 // Null pointer is a sentinel for link to the next block. |
|
428 return (mPointer + 1)->block->mPointers->ptrInfo; |
|
429 } |
|
430 return mPointer->ptrInfo; |
|
431 } |
|
432 bool operator==(const Iterator& aOther) const |
|
433 { return mPointer == aOther.mPointer; } |
|
434 bool operator!=(const Iterator& aOther) const |
|
435 { return mPointer != aOther.mPointer; } |
|
436 |
|
437 #ifdef DEBUG_CC_GRAPH |
|
438 bool Initialized() const |
|
439 { |
|
440 return mPointer != nullptr; |
|
441 } |
|
442 #endif |
|
443 |
|
444 private: |
|
445 PtrInfoOrBlock *mPointer; |
|
446 }; |
|
447 |
|
448 class Builder; |
|
449 friend class Builder; |
|
450 class Builder { |
|
451 public: |
|
452 Builder(EdgePool &aPool) |
|
453 : mCurrent(&aPool.mSentinelAndBlocks[0]), |
|
454 mBlockEnd(&aPool.mSentinelAndBlocks[0]), |
|
455 mNextBlockPtr(&aPool.Blocks()) |
|
456 { |
|
457 } |
|
458 |
|
459 Iterator Mark() { return Iterator(mCurrent); } |
|
460 |
|
461 void Add(PtrInfo* aEdge) { |
|
462 if (mCurrent == mBlockEnd) { |
|
463 Block *b = new Block(); |
|
464 *mNextBlockPtr = b; |
|
465 mCurrent = b->Start(); |
|
466 mBlockEnd = b->End(); |
|
467 mNextBlockPtr = &b->Next(); |
|
468 } |
|
469 (mCurrent++)->ptrInfo = aEdge; |
|
470 } |
|
471 private: |
|
472 // mBlockEnd points to space for null sentinel |
|
473 PtrInfoOrBlock *mCurrent, *mBlockEnd; |
|
474 Block **mNextBlockPtr; |
|
475 }; |
|
476 |
|
477 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { |
|
478 size_t n = 0; |
|
479 Block *b = Blocks(); |
|
480 while (b) { |
|
481 n += aMallocSizeOf(b); |
|
482 b = b->Next(); |
|
483 } |
|
484 return n; |
|
485 } |
|
486 }; |
|
487 |
|
488 #ifdef DEBUG_CC_GRAPH |
|
489 #define CC_GRAPH_ASSERT(b) MOZ_ASSERT(b) |
|
490 #else |
|
491 #define CC_GRAPH_ASSERT(b) |
|
492 #endif |
|
493 |
|
494 #define CC_TELEMETRY(_name, _value) \ |
|
495 PR_BEGIN_MACRO \ |
|
496 if (NS_IsMainThread()) { \ |
|
497 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \ |
|
498 } else { \ |
|
499 Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \ |
|
500 } \ |
|
501 PR_END_MACRO |
|
502 |
|
503 enum NodeColor { black, white, grey }; |
|
504 |
|
505 // This structure should be kept as small as possible; we may expect |
|
506 // hundreds of thousands of them to be allocated and touched |
|
507 // repeatedly during each cycle collection. |
|
508 |
|
509 struct PtrInfo |
|
510 { |
|
511 void *mPointer; |
|
512 nsCycleCollectionParticipant *mParticipant; |
|
513 uint32_t mColor : 2; |
|
514 uint32_t mInternalRefs : 30; |
|
515 uint32_t mRefCount; |
|
516 private: |
|
517 EdgePool::Iterator mFirstChild; |
|
518 |
|
519 public: |
|
520 |
|
521 PtrInfo(void *aPointer, nsCycleCollectionParticipant *aParticipant) |
|
522 : mPointer(aPointer), |
|
523 mParticipant(aParticipant), |
|
524 mColor(grey), |
|
525 mInternalRefs(0), |
|
526 mRefCount(UINT32_MAX - 1), |
|
527 mFirstChild() |
|
528 { |
|
529 // We initialize mRefCount to a large non-zero value so |
|
530 // that it doesn't look like a JS object to the cycle collector |
|
531 // in the case where the object dies before being traversed. |
|
532 |
|
533 MOZ_ASSERT(aParticipant); |
|
534 } |
|
535 |
|
536 // Allow NodePool::Block's constructor to compile. |
|
537 PtrInfo() { |
|
538 NS_NOTREACHED("should never be called"); |
|
539 } |
|
540 |
|
541 EdgePool::Iterator FirstChild() |
|
542 { |
|
543 CC_GRAPH_ASSERT(mFirstChild.Initialized()); |
|
544 return mFirstChild; |
|
545 } |
|
546 |
|
547 // this PtrInfo must be part of a NodePool |
|
548 EdgePool::Iterator LastChild() |
|
549 { |
|
550 CC_GRAPH_ASSERT((this + 1)->mFirstChild.Initialized()); |
|
551 return (this + 1)->mFirstChild; |
|
552 } |
|
553 |
|
554 void SetFirstChild(EdgePool::Iterator aFirstChild) |
|
555 { |
|
556 CC_GRAPH_ASSERT(aFirstChild.Initialized()); |
|
557 mFirstChild = aFirstChild; |
|
558 } |
|
559 |
|
560 // this PtrInfo must be part of a NodePool |
|
561 void SetLastChild(EdgePool::Iterator aLastChild) |
|
562 { |
|
563 CC_GRAPH_ASSERT(aLastChild.Initialized()); |
|
564 (this + 1)->mFirstChild = aLastChild; |
|
565 } |
|
566 }; |
|
567 |
|
568 /** |
|
569 * A structure designed to be used like a linked list of PtrInfo, except |
|
570 * that allocates the PtrInfo 32K-at-a-time. |
|
571 */ |
|
572 class NodePool |
|
573 { |
|
574 private: |
|
575 enum { BlockSize = 8 * 1024 }; // could be int template parameter |
|
576 |
|
577 struct Block { |
|
578 // We create and destroy Block using NS_Alloc/NS_Free rather |
|
579 // than new and delete to avoid calling its constructor and |
|
580 // destructor. |
|
581 Block() { NS_NOTREACHED("should never be called"); } |
|
582 ~Block() { NS_NOTREACHED("should never be called"); } |
|
583 |
|
584 Block* mNext; |
|
585 PtrInfo mEntries[BlockSize + 1]; // +1 to store last child of last node |
|
586 }; |
|
587 |
|
588 public: |
|
589 NodePool() |
|
590 : mBlocks(nullptr), |
|
591 mLast(nullptr) |
|
592 { |
|
593 } |
|
594 |
|
595 ~NodePool() |
|
596 { |
|
597 MOZ_ASSERT(!mBlocks, "Didn't call Clear()?"); |
|
598 } |
|
599 |
|
600 void Clear() |
|
601 { |
|
602 Block *b = mBlocks; |
|
603 while (b) { |
|
604 Block *n = b->mNext; |
|
605 NS_Free(b); |
|
606 b = n; |
|
607 } |
|
608 |
|
609 mBlocks = nullptr; |
|
610 mLast = nullptr; |
|
611 } |
|
612 |
|
613 #ifdef DEBUG |
|
614 bool IsEmpty() |
|
615 { |
|
616 return !mBlocks && !mLast; |
|
617 } |
|
618 #endif |
|
619 |
|
620 class Builder; |
|
621 friend class Builder; |
|
622 class Builder { |
|
623 public: |
|
624 Builder(NodePool& aPool) |
|
625 : mNextBlock(&aPool.mBlocks), |
|
626 mNext(aPool.mLast), |
|
627 mBlockEnd(nullptr) |
|
628 { |
|
629 MOZ_ASSERT(aPool.mBlocks == nullptr && aPool.mLast == nullptr, |
|
630 "pool not empty"); |
|
631 } |
|
632 PtrInfo *Add(void *aPointer, nsCycleCollectionParticipant *aParticipant) |
|
633 { |
|
634 if (mNext == mBlockEnd) { |
|
635 Block *block = static_cast<Block*>(NS_Alloc(sizeof(Block))); |
|
636 *mNextBlock = block; |
|
637 mNext = block->mEntries; |
|
638 mBlockEnd = block->mEntries + BlockSize; |
|
639 block->mNext = nullptr; |
|
640 mNextBlock = &block->mNext; |
|
641 } |
|
642 return new (mNext++) PtrInfo(aPointer, aParticipant); |
|
643 } |
|
644 private: |
|
645 Block **mNextBlock; |
|
646 PtrInfo *&mNext; |
|
647 PtrInfo *mBlockEnd; |
|
648 }; |
|
649 |
|
650 class Enumerator; |
|
651 friend class Enumerator; |
|
652 class Enumerator { |
|
653 public: |
|
654 Enumerator(NodePool& aPool) |
|
655 : mFirstBlock(aPool.mBlocks), |
|
656 mCurBlock(nullptr), |
|
657 mNext(nullptr), |
|
658 mBlockEnd(nullptr), |
|
659 mLast(aPool.mLast) |
|
660 { |
|
661 } |
|
662 |
|
663 bool IsDone() const |
|
664 { |
|
665 return mNext == mLast; |
|
666 } |
|
667 |
|
668 bool AtBlockEnd() const |
|
669 { |
|
670 return mNext == mBlockEnd; |
|
671 } |
|
672 |
|
673 PtrInfo* GetNext() |
|
674 { |
|
675 MOZ_ASSERT(!IsDone(), "calling GetNext when done"); |
|
676 if (mNext == mBlockEnd) { |
|
677 Block *nextBlock = mCurBlock ? mCurBlock->mNext : mFirstBlock; |
|
678 mNext = nextBlock->mEntries; |
|
679 mBlockEnd = mNext + BlockSize; |
|
680 mCurBlock = nextBlock; |
|
681 } |
|
682 return mNext++; |
|
683 } |
|
684 private: |
|
685 // mFirstBlock is a reference to allow an Enumerator to be constructed |
|
686 // for an empty graph. |
|
687 Block *&mFirstBlock; |
|
688 Block *mCurBlock; |
|
689 // mNext is the next value we want to return, unless mNext == mBlockEnd |
|
690 // NB: mLast is a reference to allow enumerating while building! |
|
691 PtrInfo *mNext, *mBlockEnd, *&mLast; |
|
692 }; |
|
693 |
|
694 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { |
|
695 // We don't measure the things pointed to by mEntries[] because those |
|
696 // pointers are non-owning. |
|
697 size_t n = 0; |
|
698 Block *b = mBlocks; |
|
699 while (b) { |
|
700 n += aMallocSizeOf(b); |
|
701 b = b->mNext; |
|
702 } |
|
703 return n; |
|
704 } |
|
705 |
|
706 private: |
|
707 Block *mBlocks; |
|
708 PtrInfo *mLast; |
|
709 }; |
|
710 |
|
711 |
|
712 // Declarations for mPtrToNodeMap. |
|
713 |
|
714 struct PtrToNodeEntry : public PLDHashEntryHdr |
|
715 { |
|
716 // The key is mNode->mPointer |
|
717 PtrInfo *mNode; |
|
718 }; |
|
719 |
|
720 static bool |
|
721 PtrToNodeMatchEntry(PLDHashTable *table, |
|
722 const PLDHashEntryHdr *entry, |
|
723 const void *key) |
|
724 { |
|
725 const PtrToNodeEntry *n = static_cast<const PtrToNodeEntry*>(entry); |
|
726 return n->mNode->mPointer == key; |
|
727 } |
|
728 |
|
729 static PLDHashTableOps PtrNodeOps = { |
|
730 PL_DHashAllocTable, |
|
731 PL_DHashFreeTable, |
|
732 PL_DHashVoidPtrKeyStub, |
|
733 PtrToNodeMatchEntry, |
|
734 PL_DHashMoveEntryStub, |
|
735 PL_DHashClearEntryStub, |
|
736 PL_DHashFinalizeStub, |
|
737 nullptr |
|
738 }; |
|
739 |
|
740 |
|
741 struct WeakMapping |
|
742 { |
|
743 // map and key will be null if the corresponding objects are GC marked |
|
744 PtrInfo *mMap; |
|
745 PtrInfo *mKey; |
|
746 PtrInfo *mKeyDelegate; |
|
747 PtrInfo *mVal; |
|
748 }; |
|
749 |
|
750 class GCGraphBuilder; |
|
751 |
|
752 struct GCGraph |
|
753 { |
|
754 NodePool mNodes; |
|
755 EdgePool mEdges; |
|
756 nsTArray<WeakMapping> mWeakMaps; |
|
757 uint32_t mRootCount; |
|
758 |
|
759 private: |
|
760 PLDHashTable mPtrToNodeMap; |
|
761 |
|
762 public: |
|
763 GCGraph() : mRootCount(0) |
|
764 { |
|
765 mPtrToNodeMap.ops = nullptr; |
|
766 } |
|
767 |
|
768 ~GCGraph() |
|
769 { |
|
770 if (mPtrToNodeMap.ops) { |
|
771 PL_DHashTableFinish(&mPtrToNodeMap); |
|
772 } |
|
773 } |
|
774 |
|
775 void Init() |
|
776 { |
|
777 MOZ_ASSERT(IsEmpty(), "Failed to call GCGraph::Clear"); |
|
778 PL_DHashTableInit(&mPtrToNodeMap, &PtrNodeOps, nullptr, |
|
779 sizeof(PtrToNodeEntry), 32768); |
|
780 } |
|
781 |
|
782 void Clear() |
|
783 { |
|
784 mNodes.Clear(); |
|
785 mEdges.Clear(); |
|
786 mWeakMaps.Clear(); |
|
787 mRootCount = 0; |
|
788 PL_DHashTableFinish(&mPtrToNodeMap); |
|
789 mPtrToNodeMap.ops = nullptr; |
|
790 } |
|
791 |
|
792 #ifdef DEBUG |
|
793 bool IsEmpty() |
|
794 { |
|
795 return mNodes.IsEmpty() && mEdges.IsEmpty() && |
|
796 mWeakMaps.IsEmpty() && mRootCount == 0 && |
|
797 !mPtrToNodeMap.ops; |
|
798 } |
|
799 #endif |
|
800 |
|
801 PtrInfo* FindNode(void *aPtr); |
|
802 PtrToNodeEntry* AddNodeToMap(void *aPtr); |
|
803 void RemoveNodeFromMap(void *aPtr); |
|
804 |
|
805 uint32_t MapCount() const |
|
806 { |
|
807 return mPtrToNodeMap.entryCount; |
|
808 } |
|
809 |
|
810 void SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
|
811 size_t *aNodesSize, size_t *aEdgesSize, |
|
812 size_t *aWeakMapsSize) const { |
|
813 *aNodesSize = mNodes.SizeOfExcludingThis(aMallocSizeOf); |
|
814 *aEdgesSize = mEdges.SizeOfExcludingThis(aMallocSizeOf); |
|
815 |
|
816 // We don't measure what the WeakMappings point to, because the |
|
817 // pointers are non-owning. |
|
818 *aWeakMapsSize = mWeakMaps.SizeOfExcludingThis(aMallocSizeOf); |
|
819 } |
|
820 }; |
|
821 |
|
822 PtrInfo* |
|
823 GCGraph::FindNode(void *aPtr) |
|
824 { |
|
825 PtrToNodeEntry *e = static_cast<PtrToNodeEntry*>(PL_DHashTableOperate(&mPtrToNodeMap, aPtr, PL_DHASH_LOOKUP)); |
|
826 if (!PL_DHASH_ENTRY_IS_BUSY(e)) { |
|
827 return nullptr; |
|
828 } |
|
829 return e->mNode; |
|
830 } |
|
831 |
|
832 PtrToNodeEntry* |
|
833 GCGraph::AddNodeToMap(void *aPtr) |
|
834 { |
|
835 PtrToNodeEntry *e = static_cast<PtrToNodeEntry*>(PL_DHashTableOperate(&mPtrToNodeMap, aPtr, PL_DHASH_ADD)); |
|
836 if (!e) { |
|
837 // Caller should track OOMs |
|
838 return nullptr; |
|
839 } |
|
840 return e; |
|
841 } |
|
842 |
|
843 void |
|
844 GCGraph::RemoveNodeFromMap(void *aPtr) |
|
845 { |
|
846 PL_DHashTableOperate(&mPtrToNodeMap, aPtr, PL_DHASH_REMOVE); |
|
847 } |
|
848 |
|
849 |
|
850 static nsISupports * |
|
851 CanonicalizeXPCOMParticipant(nsISupports *in) |
|
852 { |
|
853 nsISupports* out; |
|
854 in->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), |
|
855 reinterpret_cast<void**>(&out)); |
|
856 return out; |
|
857 } |
|
858 |
|
859 static inline void |
|
860 ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp); |
|
861 |
|
862 static void |
|
863 CanonicalizeParticipant(void **parti, nsCycleCollectionParticipant **cp) |
|
864 { |
|
865 // If the participant is null, this is an nsISupports participant, |
|
866 // so we must QI to get the real participant. |
|
867 |
|
868 if (!*cp) { |
|
869 nsISupports *nsparti = static_cast<nsISupports*>(*parti); |
|
870 nsparti = CanonicalizeXPCOMParticipant(nsparti); |
|
871 NS_ASSERTION(nsparti, |
|
872 "Don't add objects that don't participate in collection!"); |
|
873 nsXPCOMCycleCollectionParticipant *xcp; |
|
874 ToParticipant(nsparti, &xcp); |
|
875 *parti = nsparti; |
|
876 *cp = xcp; |
|
877 } |
|
878 } |
|
879 |
|
880 struct nsPurpleBufferEntry { |
|
881 union { |
|
882 void *mObject; // when low bit unset |
|
883 nsPurpleBufferEntry *mNextInFreeList; // when low bit set |
|
884 }; |
|
885 |
|
886 nsCycleCollectingAutoRefCnt *mRefCnt; |
|
887 |
|
888 nsCycleCollectionParticipant *mParticipant; // nullptr for nsISupports |
|
889 }; |
|
890 |
|
891 class nsCycleCollector; |
|
892 |
|
893 struct nsPurpleBuffer |
|
894 { |
|
895 private: |
|
896 struct Block { |
|
897 Block *mNext; |
|
898 // Try to match the size of a jemalloc bucket, to minimize slop bytes. |
|
899 // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries |
|
900 // is 16,380 bytes, which leaves 4 bytes for mNext. |
|
901 // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries |
|
902 // is 32,544 bytes, which leaves 8 bytes for mNext. |
|
903 nsPurpleBufferEntry mEntries[1365]; |
|
904 |
|
905 Block() : mNext(nullptr) { |
|
906 // Ensure Block is the right size (see above). |
|
907 static_assert( |
|
908 sizeof(Block) == 16384 || // 32-bit |
|
909 sizeof(Block) == 32768, // 64-bit |
|
910 "ill-sized nsPurpleBuffer::Block" |
|
911 ); |
|
912 } |
|
913 |
|
914 template <class PurpleVisitor> |
|
915 void VisitEntries(nsPurpleBuffer &aBuffer, PurpleVisitor &aVisitor) |
|
916 { |
|
917 nsPurpleBufferEntry *eEnd = ArrayEnd(mEntries); |
|
918 for (nsPurpleBufferEntry *e = mEntries; e != eEnd; ++e) { |
|
919 if (!(uintptr_t(e->mObject) & uintptr_t(1))) { |
|
920 aVisitor.Visit(aBuffer, e); |
|
921 } |
|
922 } |
|
923 } |
|
924 }; |
|
925 // This class wraps a linked list of the elements in the purple |
|
926 // buffer. |
|
927 |
|
928 uint32_t mCount; |
|
929 Block mFirstBlock; |
|
930 nsPurpleBufferEntry *mFreeList; |
|
931 |
|
932 public: |
|
933 nsPurpleBuffer() |
|
934 { |
|
935 InitBlocks(); |
|
936 } |
|
937 |
|
938 ~nsPurpleBuffer() |
|
939 { |
|
940 FreeBlocks(); |
|
941 } |
|
942 |
|
943 template <class PurpleVisitor> |
|
944 void VisitEntries(PurpleVisitor &aVisitor) |
|
945 { |
|
946 for (Block *b = &mFirstBlock; b; b = b->mNext) { |
|
947 b->VisitEntries(*this, aVisitor); |
|
948 } |
|
949 } |
|
950 |
|
951 void InitBlocks() |
|
952 { |
|
953 mCount = 0; |
|
954 mFreeList = nullptr; |
|
955 StartBlock(&mFirstBlock); |
|
956 } |
|
957 |
|
958 void StartBlock(Block *aBlock) |
|
959 { |
|
960 NS_ABORT_IF_FALSE(!mFreeList, "should not have free list"); |
|
961 |
|
962 // Put all the entries in the block on the free list. |
|
963 nsPurpleBufferEntry *entries = aBlock->mEntries; |
|
964 mFreeList = entries; |
|
965 for (uint32_t i = 1; i < ArrayLength(aBlock->mEntries); ++i) { |
|
966 entries[i - 1].mNextInFreeList = |
|
967 (nsPurpleBufferEntry*)(uintptr_t(entries + i) | 1); |
|
968 } |
|
969 entries[ArrayLength(aBlock->mEntries) - 1].mNextInFreeList = |
|
970 (nsPurpleBufferEntry*)1; |
|
971 } |
|
972 |
|
973 void FreeBlocks() |
|
974 { |
|
975 if (mCount > 0) |
|
976 UnmarkRemainingPurple(&mFirstBlock); |
|
977 Block *b = mFirstBlock.mNext; |
|
978 while (b) { |
|
979 if (mCount > 0) |
|
980 UnmarkRemainingPurple(b); |
|
981 Block *next = b->mNext; |
|
982 delete b; |
|
983 b = next; |
|
984 } |
|
985 mFirstBlock.mNext = nullptr; |
|
986 } |
|
987 |
|
988 struct UnmarkRemainingPurpleVisitor |
|
989 { |
|
990 void |
|
991 Visit(nsPurpleBuffer &aBuffer, nsPurpleBufferEntry *aEntry) |
|
992 { |
|
993 if (aEntry->mRefCnt) { |
|
994 aEntry->mRefCnt->RemoveFromPurpleBuffer(); |
|
995 aEntry->mRefCnt = nullptr; |
|
996 } |
|
997 aEntry->mObject = nullptr; |
|
998 --aBuffer.mCount; |
|
999 } |
|
1000 }; |
|
1001 |
|
1002 void UnmarkRemainingPurple(Block *b) |
|
1003 { |
|
1004 UnmarkRemainingPurpleVisitor visitor; |
|
1005 b->VisitEntries(*this, visitor); |
|
1006 } |
|
1007 |
|
1008 void SelectPointers(GCGraphBuilder &builder); |
|
1009 |
|
1010 // RemoveSkippable removes entries from the purple buffer synchronously |
|
1011 // (1) if aAsyncSnowWhiteFreeing is false and nsPurpleBufferEntry::mRefCnt is 0 or |
|
1012 // (2) if the object's nsXPCOMCycleCollectionParticipant::CanSkip() returns true or |
|
1013 // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false. |
|
1014 // (4) If removeChildlessNodes is true, then any nodes in the purple buffer |
|
1015 // that will have no children in the cycle collector graph will also be |
|
1016 // removed. CanSkip() may be run on these children. |
|
1017 void RemoveSkippable(nsCycleCollector* aCollector, |
|
1018 bool removeChildlessNodes, |
|
1019 bool aAsyncSnowWhiteFreeing, |
|
1020 CC_ForgetSkippableCallback aCb); |
|
1021 |
|
1022 MOZ_ALWAYS_INLINE nsPurpleBufferEntry* NewEntry() |
|
1023 { |
|
1024 if (MOZ_UNLIKELY(!mFreeList)) { |
|
1025 Block *b = new Block; |
|
1026 StartBlock(b); |
|
1027 |
|
1028 // Add the new block as the second block in the list. |
|
1029 b->mNext = mFirstBlock.mNext; |
|
1030 mFirstBlock.mNext = b; |
|
1031 } |
|
1032 |
|
1033 nsPurpleBufferEntry *e = mFreeList; |
|
1034 mFreeList = (nsPurpleBufferEntry*) |
|
1035 (uintptr_t(mFreeList->mNextInFreeList) & ~uintptr_t(1)); |
|
1036 return e; |
|
1037 } |
|
1038 |
|
1039 MOZ_ALWAYS_INLINE void Put(void *p, nsCycleCollectionParticipant *cp, |
|
1040 nsCycleCollectingAutoRefCnt *aRefCnt) |
|
1041 { |
|
1042 nsPurpleBufferEntry *e = NewEntry(); |
|
1043 |
|
1044 ++mCount; |
|
1045 |
|
1046 e->mObject = p; |
|
1047 e->mRefCnt = aRefCnt; |
|
1048 e->mParticipant = cp; |
|
1049 } |
|
1050 |
|
1051 void Remove(nsPurpleBufferEntry *e) |
|
1052 { |
|
1053 MOZ_ASSERT(mCount != 0, "must have entries"); |
|
1054 |
|
1055 if (e->mRefCnt) { |
|
1056 e->mRefCnt->RemoveFromPurpleBuffer(); |
|
1057 e->mRefCnt = nullptr; |
|
1058 } |
|
1059 e->mNextInFreeList = |
|
1060 (nsPurpleBufferEntry*)(uintptr_t(mFreeList) | uintptr_t(1)); |
|
1061 mFreeList = e; |
|
1062 |
|
1063 --mCount; |
|
1064 } |
|
1065 |
|
1066 uint32_t Count() const |
|
1067 { |
|
1068 return mCount; |
|
1069 } |
|
1070 |
|
1071 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
1072 { |
|
1073 size_t n = 0; |
|
1074 |
|
1075 // Don't measure mFirstBlock because it's within |this|. |
|
1076 const Block *block = mFirstBlock.mNext; |
|
1077 while (block) { |
|
1078 n += aMallocSizeOf(block); |
|
1079 block = block->mNext; |
|
1080 } |
|
1081 |
|
1082 // mFreeList is deliberately not measured because it points into |
|
1083 // the purple buffer, which is within mFirstBlock and thus within |this|. |
|
1084 // |
|
1085 // We also don't measure the things pointed to by mEntries[] because |
|
1086 // those pointers are non-owning. |
|
1087 |
|
1088 return n; |
|
1089 } |
|
1090 }; |
|
1091 |
|
1092 static bool |
|
1093 AddPurpleRoot(GCGraphBuilder &aBuilder, void *aRoot, nsCycleCollectionParticipant *aParti); |
|
1094 |
|
1095 struct SelectPointersVisitor |
|
1096 { |
|
1097 SelectPointersVisitor(GCGraphBuilder &aBuilder) |
|
1098 : mBuilder(aBuilder) |
|
1099 {} |
|
1100 |
|
1101 void |
|
1102 Visit(nsPurpleBuffer &aBuffer, nsPurpleBufferEntry *aEntry) |
|
1103 { |
|
1104 MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); |
|
1105 MOZ_ASSERT(aEntry->mRefCnt->get() != 0, |
|
1106 "SelectPointersVisitor: snow-white object in the purple buffer"); |
|
1107 if (!aEntry->mRefCnt->IsPurple() || |
|
1108 AddPurpleRoot(mBuilder, aEntry->mObject, aEntry->mParticipant)) { |
|
1109 aBuffer.Remove(aEntry); |
|
1110 } |
|
1111 } |
|
1112 |
|
1113 private: |
|
1114 GCGraphBuilder &mBuilder; |
|
1115 }; |
|
1116 |
|
1117 void |
|
1118 nsPurpleBuffer::SelectPointers(GCGraphBuilder &aBuilder) |
|
1119 { |
|
1120 SelectPointersVisitor visitor(aBuilder); |
|
1121 VisitEntries(visitor); |
|
1122 |
|
1123 NS_ASSERTION(mCount == 0, "AddPurpleRoot failed"); |
|
1124 if (mCount == 0) { |
|
1125 FreeBlocks(); |
|
1126 InitBlocks(); |
|
1127 } |
|
1128 } |
|
1129 |
|
1130 enum ccPhase { |
|
1131 IdlePhase, |
|
1132 GraphBuildingPhase, |
|
1133 ScanAndCollectWhitePhase, |
|
1134 CleanupPhase |
|
1135 }; |
|
1136 |
|
1137 enum ccType { |
|
1138 SliceCC, /* If a CC is in progress, continue it. Otherwise, start a new one. */ |
|
1139 ManualCC, /* Explicitly triggered. */ |
|
1140 ShutdownCC /* Shutdown CC, used for finding leaks. */ |
|
1141 }; |
|
1142 |
|
1143 #ifdef MOZ_NUWA_PROCESS |
|
1144 #include "ipc/Nuwa.h" |
|
1145 #endif |
|
1146 |
|
1147 //////////////////////////////////////////////////////////////////////// |
|
1148 // Top level structure for the cycle collector. |
|
1149 //////////////////////////////////////////////////////////////////////// |
|
1150 |
|
1151 typedef js::SliceBudget SliceBudget; |
|
1152 |
|
1153 class JSPurpleBuffer; |
|
1154 |
|
1155 class nsCycleCollector : public nsIMemoryReporter |
|
1156 { |
|
1157 NS_DECL_ISUPPORTS |
|
1158 NS_DECL_NSIMEMORYREPORTER |
|
1159 |
|
1160 bool mActivelyCollecting; |
|
1161 bool mFreeingSnowWhite; |
|
1162 // mScanInProgress should be false when we're collecting white objects. |
|
1163 bool mScanInProgress; |
|
1164 CycleCollectorResults mResults; |
|
1165 TimeStamp mCollectionStart; |
|
1166 |
|
1167 CycleCollectedJSRuntime *mJSRuntime; |
|
1168 |
|
1169 ccPhase mIncrementalPhase; |
|
1170 GCGraph mGraph; |
|
1171 nsAutoPtr<GCGraphBuilder> mBuilder; |
|
1172 nsAutoPtr<NodePool::Enumerator> mCurrNode; |
|
1173 nsCOMPtr<nsICycleCollectorListener> mListener; |
|
1174 |
|
1175 nsIThread* mThread; |
|
1176 |
|
1177 nsCycleCollectorParams mParams; |
|
1178 |
|
1179 uint32_t mWhiteNodeCount; |
|
1180 |
|
1181 CC_BeforeUnlinkCallback mBeforeUnlinkCB; |
|
1182 CC_ForgetSkippableCallback mForgetSkippableCB; |
|
1183 |
|
1184 nsPurpleBuffer mPurpleBuf; |
|
1185 |
|
1186 uint32_t mUnmergedNeeded; |
|
1187 uint32_t mMergedInARow; |
|
1188 |
|
1189 JSPurpleBuffer* mJSPurpleBuffer; |
|
1190 |
|
1191 public: |
|
1192 nsCycleCollector(); |
|
1193 virtual ~nsCycleCollector(); |
|
1194 |
|
1195 void RegisterJSRuntime(CycleCollectedJSRuntime *aJSRuntime); |
|
1196 void ForgetJSRuntime(); |
|
1197 |
|
1198 void SetBeforeUnlinkCallback(CC_BeforeUnlinkCallback aBeforeUnlinkCB) |
|
1199 { |
|
1200 CheckThreadSafety(); |
|
1201 mBeforeUnlinkCB = aBeforeUnlinkCB; |
|
1202 } |
|
1203 |
|
1204 void SetForgetSkippableCallback(CC_ForgetSkippableCallback aForgetSkippableCB) |
|
1205 { |
|
1206 CheckThreadSafety(); |
|
1207 mForgetSkippableCB = aForgetSkippableCB; |
|
1208 } |
|
1209 |
|
1210 void Suspect(void *n, nsCycleCollectionParticipant *cp, |
|
1211 nsCycleCollectingAutoRefCnt *aRefCnt); |
|
1212 uint32_t SuspectedCount(); |
|
1213 void ForgetSkippable(bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing); |
|
1214 bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer); |
|
1215 |
|
1216 // This method assumes its argument is already canonicalized. |
|
1217 void RemoveObjectFromGraph(void *aPtr); |
|
1218 |
|
1219 void PrepareForGarbageCollection(); |
|
1220 |
|
1221 bool Collect(ccType aCCType, |
|
1222 SliceBudget &aBudget, |
|
1223 nsICycleCollectorListener *aManualListener); |
|
1224 void Shutdown(); |
|
1225 |
|
1226 void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, |
|
1227 size_t *aObjectSize, |
|
1228 size_t *aGraphNodesSize, |
|
1229 size_t *aGraphEdgesSize, |
|
1230 size_t *aWeakMapsSize, |
|
1231 size_t *aPurpleBufferSize) const; |
|
1232 |
|
1233 JSPurpleBuffer* GetJSPurpleBuffer(); |
|
1234 private: |
|
1235 void CheckThreadSafety(); |
|
1236 void ShutdownCollect(); |
|
1237 |
|
1238 void FixGrayBits(bool aForceGC); |
|
1239 bool ShouldMergeZones(ccType aCCType); |
|
1240 |
|
1241 void BeginCollection(ccType aCCType, nsICycleCollectorListener *aManualListener); |
|
1242 void MarkRoots(SliceBudget &aBudget); |
|
1243 void ScanRoots(bool aFullySynchGraphBuild); |
|
1244 void ScanIncrementalRoots(); |
|
1245 void ScanWeakMaps(); |
|
1246 |
|
1247 // returns whether anything was collected |
|
1248 bool CollectWhite(); |
|
1249 |
|
1250 void CleanupAfterCollection(); |
|
1251 }; |
|
1252 |
|
1253 NS_IMPL_ISUPPORTS(nsCycleCollector, nsIMemoryReporter) |
|
1254 |
|
1255 /** |
|
1256 * GraphWalker is templatized over a Visitor class that must provide |
|
1257 * the following two methods: |
|
1258 * |
|
1259 * bool ShouldVisitNode(PtrInfo const *pi); |
|
1260 * void VisitNode(PtrInfo *pi); |
|
1261 */ |
|
1262 template <class Visitor> |
|
1263 class GraphWalker |
|
1264 { |
|
1265 private: |
|
1266 Visitor mVisitor; |
|
1267 |
|
1268 void DoWalk(nsDeque &aQueue); |
|
1269 |
|
1270 void CheckedPush(nsDeque &aQueue, PtrInfo *pi) |
|
1271 { |
|
1272 if (!pi) { |
|
1273 MOZ_CRASH(); |
|
1274 } |
|
1275 if (!aQueue.Push(pi, fallible_t())) { |
|
1276 mVisitor.Failed(); |
|
1277 } |
|
1278 } |
|
1279 |
|
1280 public: |
|
1281 void Walk(PtrInfo *s0); |
|
1282 void WalkFromRoots(GCGraph &aGraph); |
|
1283 // copy-constructing the visitor should be cheap, and less |
|
1284 // indirection than using a reference |
|
1285 GraphWalker(const Visitor aVisitor) : mVisitor(aVisitor) {} |
|
1286 }; |
|
1287 |
|
1288 |
|
1289 //////////////////////////////////////////////////////////////////////// |
|
1290 // The static collector struct |
|
1291 //////////////////////////////////////////////////////////////////////// |
|
1292 |
|
1293 struct CollectorData { |
|
1294 nsRefPtr<nsCycleCollector> mCollector; |
|
1295 CycleCollectedJSRuntime* mRuntime; |
|
1296 }; |
|
1297 |
|
1298 static mozilla::ThreadLocal<CollectorData*> sCollectorData; |
|
1299 |
|
1300 //////////////////////////////////////////////////////////////////////// |
|
1301 // Utility functions |
|
1302 //////////////////////////////////////////////////////////////////////// |
|
1303 |
|
1304 MOZ_NEVER_INLINE static void |
|
1305 Fault(const char *msg, const void *ptr=nullptr) |
|
1306 { |
|
1307 if (ptr) |
|
1308 printf("Fault in cycle collector: %s (ptr: %p)\n", msg, ptr); |
|
1309 else |
|
1310 printf("Fault in cycle collector: %s\n", msg); |
|
1311 |
|
1312 NS_RUNTIMEABORT("cycle collector fault"); |
|
1313 } |
|
1314 |
|
1315 static void |
|
1316 Fault(const char *msg, PtrInfo *pi) |
|
1317 { |
|
1318 Fault(msg, pi->mPointer); |
|
1319 } |
|
1320 |
|
1321 static inline void |
|
1322 ToParticipant(nsISupports *s, nsXPCOMCycleCollectionParticipant **cp) |
|
1323 { |
|
1324 // We use QI to move from an nsISupports to an |
|
1325 // nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper |
|
1326 // object that implements traversal and unlinking logic for the nsISupports |
|
1327 // in question. |
|
1328 CallQueryInterface(s, cp); |
|
1329 } |
|
1330 |
|
1331 template <class Visitor> |
|
1332 MOZ_NEVER_INLINE void |
|
1333 GraphWalker<Visitor>::Walk(PtrInfo *s0) |
|
1334 { |
|
1335 nsDeque queue; |
|
1336 CheckedPush(queue, s0); |
|
1337 DoWalk(queue); |
|
1338 } |
|
1339 |
|
1340 template <class Visitor> |
|
1341 MOZ_NEVER_INLINE void |
|
1342 GraphWalker<Visitor>::WalkFromRoots(GCGraph& aGraph) |
|
1343 { |
|
1344 nsDeque queue; |
|
1345 NodePool::Enumerator etor(aGraph.mNodes); |
|
1346 for (uint32_t i = 0; i < aGraph.mRootCount; ++i) { |
|
1347 CheckedPush(queue, etor.GetNext()); |
|
1348 } |
|
1349 DoWalk(queue); |
|
1350 } |
|
1351 |
|
1352 template <class Visitor> |
|
1353 MOZ_NEVER_INLINE void |
|
1354 GraphWalker<Visitor>::DoWalk(nsDeque &aQueue) |
|
1355 { |
|
1356 // Use a aQueue to match the breadth-first traversal used when we |
|
1357 // built the graph, for hopefully-better locality. |
|
1358 while (aQueue.GetSize() > 0) { |
|
1359 PtrInfo *pi = static_cast<PtrInfo*>(aQueue.PopFront()); |
|
1360 |
|
1361 if (pi->mParticipant && mVisitor.ShouldVisitNode(pi)) { |
|
1362 mVisitor.VisitNode(pi); |
|
1363 for (EdgePool::Iterator child = pi->FirstChild(), |
|
1364 child_end = pi->LastChild(); |
|
1365 child != child_end; ++child) { |
|
1366 CheckedPush(aQueue, *child); |
|
1367 } |
|
1368 } |
|
1369 } |
|
1370 } |
|
1371 |
|
1372 struct CCGraphDescriber : public LinkedListElement<CCGraphDescriber> |
|
1373 { |
|
1374 CCGraphDescriber() |
|
1375 : mAddress("0x"), mCnt(0), mType(eUnknown) {} |
|
1376 |
|
1377 enum Type |
|
1378 { |
|
1379 eRefCountedObject, |
|
1380 eGCedObject, |
|
1381 eGCMarkedObject, |
|
1382 eEdge, |
|
1383 eRoot, |
|
1384 eGarbage, |
|
1385 eUnknown |
|
1386 }; |
|
1387 |
|
1388 nsCString mAddress; |
|
1389 nsCString mName; |
|
1390 nsCString mCompartmentOrToAddress; |
|
1391 uint32_t mCnt; |
|
1392 Type mType; |
|
1393 }; |
|
1394 |
|
1395 class nsCycleCollectorLogger MOZ_FINAL : public nsICycleCollectorListener |
|
1396 { |
|
1397 public: |
|
1398 nsCycleCollectorLogger() : |
|
1399 mStream(nullptr), mWantAllTraces(false), |
|
1400 mDisableLog(false), mWantAfterProcessing(false) |
|
1401 { |
|
1402 } |
|
1403 ~nsCycleCollectorLogger() |
|
1404 { |
|
1405 ClearDescribers(); |
|
1406 if (mStream) { |
|
1407 MozillaUnRegisterDebugFILE(mStream); |
|
1408 fclose(mStream); |
|
1409 } |
|
1410 } |
|
1411 NS_DECL_ISUPPORTS |
|
1412 |
|
1413 void SetAllTraces() |
|
1414 { |
|
1415 mWantAllTraces = true; |
|
1416 } |
|
1417 |
|
1418 NS_IMETHOD AllTraces(nsICycleCollectorListener** aListener) |
|
1419 { |
|
1420 SetAllTraces(); |
|
1421 NS_ADDREF(*aListener = this); |
|
1422 return NS_OK; |
|
1423 } |
|
1424 |
|
1425 NS_IMETHOD GetWantAllTraces(bool* aAllTraces) |
|
1426 { |
|
1427 *aAllTraces = mWantAllTraces; |
|
1428 return NS_OK; |
|
1429 } |
|
1430 |
|
1431 NS_IMETHOD GetDisableLog(bool* aDisableLog) |
|
1432 { |
|
1433 *aDisableLog = mDisableLog; |
|
1434 return NS_OK; |
|
1435 } |
|
1436 |
|
1437 NS_IMETHOD SetDisableLog(bool aDisableLog) |
|
1438 { |
|
1439 mDisableLog = aDisableLog; |
|
1440 return NS_OK; |
|
1441 } |
|
1442 |
|
1443 NS_IMETHOD GetWantAfterProcessing(bool* aWantAfterProcessing) |
|
1444 { |
|
1445 *aWantAfterProcessing = mWantAfterProcessing; |
|
1446 return NS_OK; |
|
1447 } |
|
1448 |
|
1449 NS_IMETHOD SetWantAfterProcessing(bool aWantAfterProcessing) |
|
1450 { |
|
1451 mWantAfterProcessing = aWantAfterProcessing; |
|
1452 return NS_OK; |
|
1453 } |
|
1454 |
|
1455 NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) |
|
1456 { |
|
1457 aIdentifier = mFilenameIdentifier; |
|
1458 return NS_OK; |
|
1459 } |
|
1460 |
|
1461 NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) |
|
1462 { |
|
1463 mFilenameIdentifier = aIdentifier; |
|
1464 return NS_OK; |
|
1465 } |
|
1466 |
|
1467 NS_IMETHOD GetGcLogPath(nsAString &aPath) |
|
1468 { |
|
1469 aPath = mGCLogPath; |
|
1470 return NS_OK; |
|
1471 } |
|
1472 |
|
1473 NS_IMETHOD GetCcLogPath(nsAString &aPath) |
|
1474 { |
|
1475 aPath = mCCLogPath; |
|
1476 return NS_OK; |
|
1477 } |
|
1478 |
|
1479 NS_IMETHOD Begin() |
|
1480 { |
|
1481 mCurrentAddress.AssignLiteral("0x"); |
|
1482 ClearDescribers(); |
|
1483 if (mDisableLog) { |
|
1484 return NS_OK; |
|
1485 } |
|
1486 |
|
1487 // Initially create the log in a file starting with |
|
1488 // "incomplete-gc-edges". We'll move the file and strip off the |
|
1489 // "incomplete-" once the dump completes. (We do this because we don't |
|
1490 // want scripts which poll the filesystem looking for gc/cc dumps to |
|
1491 // grab a file before we're finished writing to it.) |
|
1492 nsCOMPtr<nsIFile> gcLogFile = CreateTempFile("incomplete-gc-edges"); |
|
1493 if (NS_WARN_IF(!gcLogFile)) |
|
1494 return NS_ERROR_UNEXPECTED; |
|
1495 |
|
1496 // Dump the JS heap. |
|
1497 FILE* gcLogANSIFile = nullptr; |
|
1498 gcLogFile->OpenANSIFileDesc("w", &gcLogANSIFile); |
|
1499 if (NS_WARN_IF(!gcLogANSIFile)) |
|
1500 return NS_ERROR_UNEXPECTED; |
|
1501 MozillaRegisterDebugFILE(gcLogANSIFile); |
|
1502 CollectorData *data = sCollectorData.get(); |
|
1503 if (data && data->mRuntime) |
|
1504 data->mRuntime->DumpJSHeap(gcLogANSIFile); |
|
1505 MozillaUnRegisterDebugFILE(gcLogANSIFile); |
|
1506 fclose(gcLogANSIFile); |
|
1507 |
|
1508 // Strip off "incomplete-". |
|
1509 nsCOMPtr<nsIFile> gcLogFileFinalDestination = |
|
1510 CreateTempFile("gc-edges"); |
|
1511 if (NS_WARN_IF(!gcLogFileFinalDestination)) |
|
1512 return NS_ERROR_UNEXPECTED; |
|
1513 |
|
1514 nsAutoString gcLogFileFinalDestinationName; |
|
1515 gcLogFileFinalDestination->GetLeafName(gcLogFileFinalDestinationName); |
|
1516 if (NS_WARN_IF(gcLogFileFinalDestinationName.IsEmpty())) |
|
1517 return NS_ERROR_UNEXPECTED; |
|
1518 |
|
1519 gcLogFile->MoveTo(/* directory */ nullptr, gcLogFileFinalDestinationName); |
|
1520 |
|
1521 // Log to the error console. |
|
1522 nsCOMPtr<nsIConsoleService> cs = |
|
1523 do_GetService(NS_CONSOLESERVICE_CONTRACTID); |
|
1524 if (cs) { |
|
1525 nsAutoString gcLogPath; |
|
1526 gcLogFileFinalDestination->GetPath(gcLogPath); |
|
1527 |
|
1528 nsString msg = NS_LITERAL_STRING("Garbage Collector log dumped to ") + |
|
1529 gcLogPath; |
|
1530 cs->LogStringMessage(msg.get()); |
|
1531 |
|
1532 mGCLogPath = gcLogPath; |
|
1533 } |
|
1534 |
|
1535 // Open a file for dumping the CC graph. We again prefix with |
|
1536 // "incomplete-". |
|
1537 mOutFile = CreateTempFile("incomplete-cc-edges"); |
|
1538 if (NS_WARN_IF(!mOutFile)) |
|
1539 return NS_ERROR_UNEXPECTED; |
|
1540 MOZ_ASSERT(!mStream); |
|
1541 mOutFile->OpenANSIFileDesc("w", &mStream); |
|
1542 if (NS_WARN_IF(!mStream)) |
|
1543 return NS_ERROR_UNEXPECTED; |
|
1544 MozillaRegisterDebugFILE(mStream); |
|
1545 |
|
1546 fprintf(mStream, "# WantAllTraces=%s\n", mWantAllTraces ? "true" : "false"); |
|
1547 |
|
1548 return NS_OK; |
|
1549 } |
|
1550 NS_IMETHOD NoteRefCountedObject(uint64_t aAddress, uint32_t refCount, |
|
1551 const char *aObjectDescription) |
|
1552 { |
|
1553 if (!mDisableLog) { |
|
1554 fprintf(mStream, "%p [rc=%u] %s\n", (void*)aAddress, refCount, |
|
1555 aObjectDescription); |
|
1556 } |
|
1557 if (mWantAfterProcessing) { |
|
1558 CCGraphDescriber* d = new CCGraphDescriber(); |
|
1559 mDescribers.insertBack(d); |
|
1560 mCurrentAddress.AssignLiteral("0x"); |
|
1561 mCurrentAddress.AppendInt(aAddress, 16); |
|
1562 d->mType = CCGraphDescriber::eRefCountedObject; |
|
1563 d->mAddress = mCurrentAddress; |
|
1564 d->mCnt = refCount; |
|
1565 d->mName.Append(aObjectDescription); |
|
1566 } |
|
1567 return NS_OK; |
|
1568 } |
|
1569 NS_IMETHOD NoteGCedObject(uint64_t aAddress, bool aMarked, |
|
1570 const char *aObjectDescription, |
|
1571 uint64_t aCompartmentAddress) |
|
1572 { |
|
1573 if (!mDisableLog) { |
|
1574 fprintf(mStream, "%p [gc%s] %s\n", (void*)aAddress, |
|
1575 aMarked ? ".marked" : "", aObjectDescription); |
|
1576 } |
|
1577 if (mWantAfterProcessing) { |
|
1578 CCGraphDescriber* d = new CCGraphDescriber(); |
|
1579 mDescribers.insertBack(d); |
|
1580 mCurrentAddress.AssignLiteral("0x"); |
|
1581 mCurrentAddress.AppendInt(aAddress, 16); |
|
1582 d->mType = aMarked ? CCGraphDescriber::eGCMarkedObject : |
|
1583 CCGraphDescriber::eGCedObject; |
|
1584 d->mAddress = mCurrentAddress; |
|
1585 d->mName.Append(aObjectDescription); |
|
1586 if (aCompartmentAddress) { |
|
1587 d->mCompartmentOrToAddress.AssignLiteral("0x"); |
|
1588 d->mCompartmentOrToAddress.AppendInt(aCompartmentAddress, 16); |
|
1589 } else { |
|
1590 d->mCompartmentOrToAddress.SetIsVoid(true); |
|
1591 } |
|
1592 } |
|
1593 return NS_OK; |
|
1594 } |
|
1595 NS_IMETHOD NoteEdge(uint64_t aToAddress, const char *aEdgeName) |
|
1596 { |
|
1597 if (!mDisableLog) { |
|
1598 fprintf(mStream, "> %p %s\n", (void*)aToAddress, aEdgeName); |
|
1599 } |
|
1600 if (mWantAfterProcessing) { |
|
1601 CCGraphDescriber* d = new CCGraphDescriber(); |
|
1602 mDescribers.insertBack(d); |
|
1603 d->mType = CCGraphDescriber::eEdge; |
|
1604 d->mAddress = mCurrentAddress; |
|
1605 d->mCompartmentOrToAddress.AssignLiteral("0x"); |
|
1606 d->mCompartmentOrToAddress.AppendInt(aToAddress, 16); |
|
1607 d->mName.Append(aEdgeName); |
|
1608 } |
|
1609 return NS_OK; |
|
1610 } |
|
1611 NS_IMETHOD NoteWeakMapEntry(uint64_t aMap, uint64_t aKey, |
|
1612 uint64_t aKeyDelegate, uint64_t aValue) |
|
1613 { |
|
1614 if (!mDisableLog) { |
|
1615 fprintf(mStream, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", |
|
1616 (void*)aMap, (void*)aKey, (void*)aKeyDelegate, (void*)aValue); |
|
1617 } |
|
1618 // We don't support after-processing for weak map entries. |
|
1619 return NS_OK; |
|
1620 } |
|
1621 NS_IMETHOD NoteIncrementalRoot(uint64_t aAddress) |
|
1622 { |
|
1623 if (!mDisableLog) { |
|
1624 fprintf(mStream, "IncrementalRoot %p\n", (void*)aAddress); |
|
1625 } |
|
1626 // We don't support after-processing for incremental roots. |
|
1627 return NS_OK; |
|
1628 } |
|
1629 NS_IMETHOD BeginResults() |
|
1630 { |
|
1631 if (!mDisableLog) { |
|
1632 fputs("==========\n", mStream); |
|
1633 } |
|
1634 return NS_OK; |
|
1635 } |
|
1636 NS_IMETHOD DescribeRoot(uint64_t aAddress, uint32_t aKnownEdges) |
|
1637 { |
|
1638 if (!mDisableLog) { |
|
1639 fprintf(mStream, "%p [known=%u]\n", (void*)aAddress, aKnownEdges); |
|
1640 } |
|
1641 if (mWantAfterProcessing) { |
|
1642 CCGraphDescriber* d = new CCGraphDescriber(); |
|
1643 mDescribers.insertBack(d); |
|
1644 d->mType = CCGraphDescriber::eRoot; |
|
1645 d->mAddress.AppendInt(aAddress, 16); |
|
1646 d->mCnt = aKnownEdges; |
|
1647 } |
|
1648 return NS_OK; |
|
1649 } |
|
1650 NS_IMETHOD DescribeGarbage(uint64_t aAddress) |
|
1651 { |
|
1652 if (!mDisableLog) { |
|
1653 fprintf(mStream, "%p [garbage]\n", (void*)aAddress); |
|
1654 } |
|
1655 if (mWantAfterProcessing) { |
|
1656 CCGraphDescriber* d = new CCGraphDescriber(); |
|
1657 mDescribers.insertBack(d); |
|
1658 d->mType = CCGraphDescriber::eGarbage; |
|
1659 d->mAddress.AppendInt(aAddress, 16); |
|
1660 } |
|
1661 return NS_OK; |
|
1662 } |
|
1663 NS_IMETHOD End() |
|
1664 { |
|
1665 if (!mDisableLog) { |
|
1666 MOZ_ASSERT(mStream); |
|
1667 MOZ_ASSERT(mOutFile); |
|
1668 |
|
1669 MozillaUnRegisterDebugFILE(mStream); |
|
1670 fclose(mStream); |
|
1671 mStream = nullptr; |
|
1672 |
|
1673 // Strip off "incomplete-" from the log file's name. |
|
1674 nsCOMPtr<nsIFile> logFileFinalDestination = |
|
1675 CreateTempFile("cc-edges"); |
|
1676 if (NS_WARN_IF(!logFileFinalDestination)) |
|
1677 return NS_ERROR_UNEXPECTED; |
|
1678 |
|
1679 nsAutoString logFileFinalDestinationName; |
|
1680 logFileFinalDestination->GetLeafName(logFileFinalDestinationName); |
|
1681 if (NS_WARN_IF(logFileFinalDestinationName.IsEmpty())) |
|
1682 return NS_ERROR_UNEXPECTED; |
|
1683 |
|
1684 mOutFile->MoveTo(/* directory = */ nullptr, |
|
1685 logFileFinalDestinationName); |
|
1686 mOutFile = nullptr; |
|
1687 |
|
1688 // Log to the error console. |
|
1689 nsCOMPtr<nsIConsoleService> cs = |
|
1690 do_GetService(NS_CONSOLESERVICE_CONTRACTID); |
|
1691 if (cs) { |
|
1692 nsAutoString ccLogPath; |
|
1693 logFileFinalDestination->GetPath(ccLogPath); |
|
1694 |
|
1695 nsString msg = NS_LITERAL_STRING("Cycle Collector log dumped to ") + |
|
1696 ccLogPath; |
|
1697 cs->LogStringMessage(msg.get()); |
|
1698 |
|
1699 mCCLogPath = ccLogPath; |
|
1700 } |
|
1701 } |
|
1702 return NS_OK; |
|
1703 } |
|
1704 NS_IMETHOD ProcessNext(nsICycleCollectorHandler* aHandler, |
|
1705 bool* aCanContinue) |
|
1706 { |
|
1707 if (NS_WARN_IF(!aHandler) || NS_WARN_IF(!mWantAfterProcessing)) |
|
1708 return NS_ERROR_UNEXPECTED; |
|
1709 CCGraphDescriber* d = mDescribers.popFirst(); |
|
1710 if (d) { |
|
1711 switch (d->mType) { |
|
1712 case CCGraphDescriber::eRefCountedObject: |
|
1713 aHandler->NoteRefCountedObject(d->mAddress, |
|
1714 d->mCnt, |
|
1715 d->mName); |
|
1716 break; |
|
1717 case CCGraphDescriber::eGCedObject: |
|
1718 case CCGraphDescriber::eGCMarkedObject: |
|
1719 aHandler->NoteGCedObject(d->mAddress, |
|
1720 d->mType == |
|
1721 CCGraphDescriber::eGCMarkedObject, |
|
1722 d->mName, |
|
1723 d->mCompartmentOrToAddress); |
|
1724 break; |
|
1725 case CCGraphDescriber::eEdge: |
|
1726 aHandler->NoteEdge(d->mAddress, |
|
1727 d->mCompartmentOrToAddress, |
|
1728 d->mName); |
|
1729 break; |
|
1730 case CCGraphDescriber::eRoot: |
|
1731 aHandler->DescribeRoot(d->mAddress, |
|
1732 d->mCnt); |
|
1733 break; |
|
1734 case CCGraphDescriber::eGarbage: |
|
1735 aHandler->DescribeGarbage(d->mAddress); |
|
1736 break; |
|
1737 case CCGraphDescriber::eUnknown: |
|
1738 NS_NOTREACHED("CCGraphDescriber::eUnknown"); |
|
1739 break; |
|
1740 } |
|
1741 delete d; |
|
1742 } |
|
1743 if (!(*aCanContinue = !mDescribers.isEmpty())) { |
|
1744 mCurrentAddress.AssignLiteral("0x"); |
|
1745 } |
|
1746 return NS_OK; |
|
1747 } |
|
1748 private: |
|
1749 /** |
|
1750 * Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in |
|
1751 * $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing |
|
1752 * file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll |
|
1753 * try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so |
|
1754 * on. |
|
1755 */ |
|
1756 already_AddRefed<nsIFile> |
|
1757 CreateTempFile(const char* aPrefix) |
|
1758 { |
|
1759 nsPrintfCString filename("%s.%d%s%s.log", |
|
1760 aPrefix, |
|
1761 base::GetCurrentProcId(), |
|
1762 mFilenameIdentifier.IsEmpty() ? "" : ".", |
|
1763 NS_ConvertUTF16toUTF8(mFilenameIdentifier).get()); |
|
1764 |
|
1765 // Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from |
|
1766 // the fallback directories in OpenTempFile. We don't use an nsCOMPtr |
|
1767 // here because OpenTempFile uses an in/out param and getter_AddRefs |
|
1768 // wouldn't work. |
|
1769 nsIFile* logFile = nullptr; |
|
1770 if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) { |
|
1771 NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, |
|
1772 &logFile); |
|
1773 } |
|
1774 |
|
1775 // In Android case, this function will open a file named aFilename under |
|
1776 // specific folder (/data/local/tmp/memory-reports). Otherwise, it will |
|
1777 // open a file named aFilename under "NS_OS_TEMP_DIR". |
|
1778 nsresult rv = nsDumpUtils::OpenTempFile( |
|
1779 filename, |
|
1780 &logFile, |
|
1781 NS_LITERAL_CSTRING("memory-reports")); |
|
1782 if (NS_FAILED(rv)) { |
|
1783 NS_IF_RELEASE(logFile); |
|
1784 return nullptr; |
|
1785 } |
|
1786 |
|
1787 return dont_AddRef(logFile); |
|
1788 } |
|
1789 |
|
1790 void ClearDescribers() |
|
1791 { |
|
1792 CCGraphDescriber* d; |
|
1793 while((d = mDescribers.popFirst())) { |
|
1794 delete d; |
|
1795 } |
|
1796 } |
|
1797 |
|
1798 FILE *mStream; |
|
1799 nsCOMPtr<nsIFile> mOutFile; |
|
1800 bool mWantAllTraces; |
|
1801 bool mDisableLog; |
|
1802 bool mWantAfterProcessing; |
|
1803 nsString mFilenameIdentifier; |
|
1804 nsString mGCLogPath; |
|
1805 nsString mCCLogPath; |
|
1806 nsCString mCurrentAddress; |
|
1807 mozilla::LinkedList<CCGraphDescriber> mDescribers; |
|
1808 }; |
|
1809 |
|
1810 NS_IMPL_ISUPPORTS(nsCycleCollectorLogger, nsICycleCollectorListener) |
|
1811 |
|
1812 nsresult |
|
1813 nsCycleCollectorLoggerConstructor(nsISupports* aOuter, |
|
1814 const nsIID& aIID, |
|
1815 void* *aInstancePtr) |
|
1816 { |
|
1817 if (NS_WARN_IF(aOuter)) |
|
1818 return NS_ERROR_NO_AGGREGATION; |
|
1819 |
|
1820 nsISupports *logger = new nsCycleCollectorLogger(); |
|
1821 |
|
1822 return logger->QueryInterface(aIID, aInstancePtr); |
|
1823 } |
|
1824 |
|
1825 //////////////////////////////////////////////////////////////////////// |
|
1826 // Bacon & Rajan's |MarkRoots| routine. |
|
1827 //////////////////////////////////////////////////////////////////////// |
|
1828 |
|
1829 class GCGraphBuilder : public nsCycleCollectionTraversalCallback, |
|
1830 public nsCycleCollectionNoteRootCallback |
|
1831 { |
|
1832 private: |
|
1833 GCGraph &mGraph; |
|
1834 CycleCollectorResults &mResults; |
|
1835 NodePool::Builder mNodeBuilder; |
|
1836 EdgePool::Builder mEdgeBuilder; |
|
1837 PtrInfo *mCurrPi; |
|
1838 nsCycleCollectionParticipant *mJSParticipant; |
|
1839 nsCycleCollectionParticipant *mJSZoneParticipant; |
|
1840 nsCString mNextEdgeName; |
|
1841 nsICycleCollectorListener *mListener; |
|
1842 bool mMergeZones; |
|
1843 bool mRanOutOfMemory; |
|
1844 |
|
1845 public: |
|
1846 GCGraphBuilder(GCGraph &aGraph, |
|
1847 CycleCollectorResults &aResults, |
|
1848 CycleCollectedJSRuntime *aJSRuntime, |
|
1849 nsICycleCollectorListener *aListener, |
|
1850 bool aMergeZones); |
|
1851 virtual ~GCGraphBuilder(); |
|
1852 |
|
1853 bool WantAllTraces() const |
|
1854 { |
|
1855 return nsCycleCollectionNoteRootCallback::WantAllTraces(); |
|
1856 } |
|
1857 |
|
1858 PtrInfo* AddNode(void *aPtr, nsCycleCollectionParticipant *aParticipant); |
|
1859 PtrInfo* AddWeakMapNode(void* node); |
|
1860 void Traverse(PtrInfo* aPtrInfo); |
|
1861 void SetLastChild(); |
|
1862 |
|
1863 bool RanOutOfMemory() const { return mRanOutOfMemory; } |
|
1864 |
|
1865 private: |
|
1866 void DescribeNode(uint32_t refCount, const char *objName) |
|
1867 { |
|
1868 mCurrPi->mRefCount = refCount; |
|
1869 } |
|
1870 |
|
1871 public: |
|
1872 // nsCycleCollectionNoteRootCallback methods. |
|
1873 NS_IMETHOD_(void) NoteXPCOMRoot(nsISupports *root); |
|
1874 NS_IMETHOD_(void) NoteJSRoot(void *root); |
|
1875 NS_IMETHOD_(void) NoteNativeRoot(void *root, nsCycleCollectionParticipant *participant); |
|
1876 NS_IMETHOD_(void) NoteWeakMapping(void *map, void *key, void *kdelegate, void *val); |
|
1877 |
|
1878 // nsCycleCollectionTraversalCallback methods. |
|
1879 NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refCount, |
|
1880 const char *objName); |
|
1881 NS_IMETHOD_(void) DescribeGCedNode(bool isMarked, const char *objName, |
|
1882 uint64_t aCompartmentAddress); |
|
1883 |
|
1884 NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child); |
|
1885 NS_IMETHOD_(void) NoteJSChild(void *child); |
|
1886 NS_IMETHOD_(void) NoteNativeChild(void *child, |
|
1887 nsCycleCollectionParticipant *participant); |
|
1888 NS_IMETHOD_(void) NoteNextEdgeName(const char* name); |
|
1889 |
|
1890 private: |
|
1891 NS_IMETHOD_(void) NoteRoot(void *root, |
|
1892 nsCycleCollectionParticipant *participant) |
|
1893 { |
|
1894 MOZ_ASSERT(root); |
|
1895 MOZ_ASSERT(participant); |
|
1896 |
|
1897 if (!participant->CanSkipInCC(root) || MOZ_UNLIKELY(WantAllTraces())) { |
|
1898 AddNode(root, participant); |
|
1899 } |
|
1900 } |
|
1901 |
|
1902 NS_IMETHOD_(void) NoteChild(void *child, nsCycleCollectionParticipant *cp, |
|
1903 nsCString edgeName) |
|
1904 { |
|
1905 PtrInfo *childPi = AddNode(child, cp); |
|
1906 if (!childPi) |
|
1907 return; |
|
1908 mEdgeBuilder.Add(childPi); |
|
1909 if (mListener) { |
|
1910 mListener->NoteEdge((uint64_t)child, edgeName.get()); |
|
1911 } |
|
1912 ++childPi->mInternalRefs; |
|
1913 } |
|
1914 |
|
1915 JS::Zone *MergeZone(void *gcthing) { |
|
1916 if (!mMergeZones) { |
|
1917 return nullptr; |
|
1918 } |
|
1919 JS::Zone *zone = JS::GetGCThingZone(gcthing); |
|
1920 if (js::IsSystemZone(zone)) { |
|
1921 return nullptr; |
|
1922 } |
|
1923 return zone; |
|
1924 } |
|
1925 }; |
|
1926 |
|
1927 GCGraphBuilder::GCGraphBuilder(GCGraph &aGraph, |
|
1928 CycleCollectorResults &aResults, |
|
1929 CycleCollectedJSRuntime *aJSRuntime, |
|
1930 nsICycleCollectorListener *aListener, |
|
1931 bool aMergeZones) |
|
1932 : mGraph(aGraph), |
|
1933 mResults(aResults), |
|
1934 mNodeBuilder(aGraph.mNodes), |
|
1935 mEdgeBuilder(aGraph.mEdges), |
|
1936 mJSParticipant(nullptr), |
|
1937 mJSZoneParticipant(nullptr), |
|
1938 mListener(aListener), |
|
1939 mMergeZones(aMergeZones), |
|
1940 mRanOutOfMemory(false) |
|
1941 { |
|
1942 if (aJSRuntime) { |
|
1943 mJSParticipant = aJSRuntime->GCThingParticipant(); |
|
1944 mJSZoneParticipant = aJSRuntime->ZoneParticipant(); |
|
1945 } |
|
1946 |
|
1947 uint32_t flags = 0; |
|
1948 if (!flags && mListener) { |
|
1949 flags = nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO; |
|
1950 bool all = false; |
|
1951 mListener->GetWantAllTraces(&all); |
|
1952 if (all) { |
|
1953 flags |= nsCycleCollectionTraversalCallback::WANT_ALL_TRACES; |
|
1954 mWantAllTraces = true; // for nsCycleCollectionNoteRootCallback |
|
1955 } |
|
1956 } |
|
1957 |
|
1958 mFlags |= flags; |
|
1959 |
|
1960 mMergeZones = mMergeZones && MOZ_LIKELY(!WantAllTraces()); |
|
1961 |
|
1962 MOZ_ASSERT(nsCycleCollectionNoteRootCallback::WantAllTraces() == |
|
1963 nsCycleCollectionTraversalCallback::WantAllTraces()); |
|
1964 } |
|
1965 |
|
1966 GCGraphBuilder::~GCGraphBuilder() |
|
1967 { |
|
1968 } |
|
1969 |
|
1970 PtrInfo* |
|
1971 GCGraphBuilder::AddNode(void *aPtr, nsCycleCollectionParticipant *aParticipant) |
|
1972 { |
|
1973 PtrToNodeEntry *e = mGraph.AddNodeToMap(aPtr); |
|
1974 if (!e) { |
|
1975 mRanOutOfMemory = true; |
|
1976 return nullptr; |
|
1977 } |
|
1978 |
|
1979 PtrInfo *result; |
|
1980 if (!e->mNode) { |
|
1981 // New entry. |
|
1982 result = mNodeBuilder.Add(aPtr, aParticipant); |
|
1983 e->mNode = result; |
|
1984 NS_ASSERTION(result, "mNodeBuilder.Add returned null"); |
|
1985 } else { |
|
1986 result = e->mNode; |
|
1987 MOZ_ASSERT(result->mParticipant == aParticipant, |
|
1988 "nsCycleCollectionParticipant shouldn't change!"); |
|
1989 } |
|
1990 return result; |
|
1991 } |
|
1992 |
|
1993 MOZ_NEVER_INLINE void |
|
1994 GCGraphBuilder::Traverse(PtrInfo* aPtrInfo) |
|
1995 { |
|
1996 mCurrPi = aPtrInfo; |
|
1997 |
|
1998 mCurrPi->SetFirstChild(mEdgeBuilder.Mark()); |
|
1999 |
|
2000 if (!aPtrInfo->mParticipant) { |
|
2001 return; |
|
2002 } |
|
2003 |
|
2004 nsresult rv = aPtrInfo->mParticipant->Traverse(aPtrInfo->mPointer, *this); |
|
2005 if (NS_FAILED(rv)) { |
|
2006 Fault("script pointer traversal failed", aPtrInfo); |
|
2007 } |
|
2008 } |
|
2009 |
|
2010 void |
|
2011 GCGraphBuilder::SetLastChild() |
|
2012 { |
|
2013 mCurrPi->SetLastChild(mEdgeBuilder.Mark()); |
|
2014 } |
|
2015 |
|
2016 NS_IMETHODIMP_(void) |
|
2017 GCGraphBuilder::NoteXPCOMRoot(nsISupports *root) |
|
2018 { |
|
2019 root = CanonicalizeXPCOMParticipant(root); |
|
2020 NS_ASSERTION(root, |
|
2021 "Don't add objects that don't participate in collection!"); |
|
2022 |
|
2023 nsXPCOMCycleCollectionParticipant *cp; |
|
2024 ToParticipant(root, &cp); |
|
2025 |
|
2026 NoteRoot(root, cp); |
|
2027 } |
|
2028 |
|
2029 NS_IMETHODIMP_(void) |
|
2030 GCGraphBuilder::NoteJSRoot(void *root) |
|
2031 { |
|
2032 if (JS::Zone *zone = MergeZone(root)) { |
|
2033 NoteRoot(zone, mJSZoneParticipant); |
|
2034 } else { |
|
2035 NoteRoot(root, mJSParticipant); |
|
2036 } |
|
2037 } |
|
2038 |
|
2039 NS_IMETHODIMP_(void) |
|
2040 GCGraphBuilder::NoteNativeRoot(void *root, nsCycleCollectionParticipant *participant) |
|
2041 { |
|
2042 NoteRoot(root, participant); |
|
2043 } |
|
2044 |
|
2045 NS_IMETHODIMP_(void) |
|
2046 GCGraphBuilder::DescribeRefCountedNode(nsrefcnt refCount, const char *objName) |
|
2047 { |
|
2048 if (refCount == 0) |
|
2049 Fault("zero refcount", mCurrPi); |
|
2050 if (refCount == UINT32_MAX) |
|
2051 Fault("overflowing refcount", mCurrPi); |
|
2052 mResults.mVisitedRefCounted++; |
|
2053 |
|
2054 if (mListener) { |
|
2055 mListener->NoteRefCountedObject((uint64_t)mCurrPi->mPointer, refCount, |
|
2056 objName); |
|
2057 } |
|
2058 |
|
2059 DescribeNode(refCount, objName); |
|
2060 } |
|
2061 |
|
2062 NS_IMETHODIMP_(void) |
|
2063 GCGraphBuilder::DescribeGCedNode(bool isMarked, const char *objName, |
|
2064 uint64_t aCompartmentAddress) |
|
2065 { |
|
2066 uint32_t refCount = isMarked ? UINT32_MAX : 0; |
|
2067 mResults.mVisitedGCed++; |
|
2068 |
|
2069 if (mListener) { |
|
2070 mListener->NoteGCedObject((uint64_t)mCurrPi->mPointer, isMarked, |
|
2071 objName, aCompartmentAddress); |
|
2072 } |
|
2073 |
|
2074 DescribeNode(refCount, objName); |
|
2075 } |
|
2076 |
|
2077 NS_IMETHODIMP_(void) |
|
2078 GCGraphBuilder::NoteXPCOMChild(nsISupports *child) |
|
2079 { |
|
2080 nsCString edgeName; |
|
2081 if (WantDebugInfo()) { |
|
2082 edgeName.Assign(mNextEdgeName); |
|
2083 mNextEdgeName.Truncate(); |
|
2084 } |
|
2085 if (!child || !(child = CanonicalizeXPCOMParticipant(child))) |
|
2086 return; |
|
2087 |
|
2088 nsXPCOMCycleCollectionParticipant *cp; |
|
2089 ToParticipant(child, &cp); |
|
2090 if (cp && (!cp->CanSkipThis(child) || WantAllTraces())) { |
|
2091 NoteChild(child, cp, edgeName); |
|
2092 } |
|
2093 } |
|
2094 |
|
2095 NS_IMETHODIMP_(void) |
|
2096 GCGraphBuilder::NoteNativeChild(void *child, |
|
2097 nsCycleCollectionParticipant *participant) |
|
2098 { |
|
2099 nsCString edgeName; |
|
2100 if (WantDebugInfo()) { |
|
2101 edgeName.Assign(mNextEdgeName); |
|
2102 mNextEdgeName.Truncate(); |
|
2103 } |
|
2104 if (!child) |
|
2105 return; |
|
2106 |
|
2107 MOZ_ASSERT(participant, "Need a nsCycleCollectionParticipant!"); |
|
2108 NoteChild(child, participant, edgeName); |
|
2109 } |
|
2110 |
|
2111 NS_IMETHODIMP_(void) |
|
2112 GCGraphBuilder::NoteJSChild(void *child) |
|
2113 { |
|
2114 if (!child) { |
|
2115 return; |
|
2116 } |
|
2117 |
|
2118 nsCString edgeName; |
|
2119 if (MOZ_UNLIKELY(WantDebugInfo())) { |
|
2120 edgeName.Assign(mNextEdgeName); |
|
2121 mNextEdgeName.Truncate(); |
|
2122 } |
|
2123 |
|
2124 if (xpc_GCThingIsGrayCCThing(child) || MOZ_UNLIKELY(WantAllTraces())) { |
|
2125 if (JS::Zone *zone = MergeZone(child)) { |
|
2126 NoteChild(zone, mJSZoneParticipant, edgeName); |
|
2127 } else { |
|
2128 NoteChild(child, mJSParticipant, edgeName); |
|
2129 } |
|
2130 } |
|
2131 } |
|
2132 |
|
2133 NS_IMETHODIMP_(void) |
|
2134 GCGraphBuilder::NoteNextEdgeName(const char* name) |
|
2135 { |
|
2136 if (WantDebugInfo()) { |
|
2137 mNextEdgeName = name; |
|
2138 } |
|
2139 } |
|
2140 |
|
2141 PtrInfo* |
|
2142 GCGraphBuilder::AddWeakMapNode(void *node) |
|
2143 { |
|
2144 MOZ_ASSERT(node, "Weak map node should be non-null."); |
|
2145 |
|
2146 if (!xpc_GCThingIsGrayCCThing(node) && !WantAllTraces()) |
|
2147 return nullptr; |
|
2148 |
|
2149 if (JS::Zone *zone = MergeZone(node)) { |
|
2150 return AddNode(zone, mJSZoneParticipant); |
|
2151 } else { |
|
2152 return AddNode(node, mJSParticipant); |
|
2153 } |
|
2154 } |
|
2155 |
|
2156 NS_IMETHODIMP_(void) |
|
2157 GCGraphBuilder::NoteWeakMapping(void *map, void *key, void *kdelegate, void *val) |
|
2158 { |
|
2159 // Don't try to optimize away the entry here, as we've already attempted to |
|
2160 // do that in TraceWeakMapping in nsXPConnect. |
|
2161 WeakMapping *mapping = mGraph.mWeakMaps.AppendElement(); |
|
2162 mapping->mMap = map ? AddWeakMapNode(map) : nullptr; |
|
2163 mapping->mKey = key ? AddWeakMapNode(key) : nullptr; |
|
2164 mapping->mKeyDelegate = kdelegate ? AddWeakMapNode(kdelegate) : mapping->mKey; |
|
2165 mapping->mVal = val ? AddWeakMapNode(val) : nullptr; |
|
2166 |
|
2167 if (mListener) { |
|
2168 mListener->NoteWeakMapEntry((uint64_t)map, (uint64_t)key, |
|
2169 (uint64_t)kdelegate, (uint64_t)val); |
|
2170 } |
|
2171 } |
|
2172 |
|
2173 static bool |
|
2174 AddPurpleRoot(GCGraphBuilder &aBuilder, void *aRoot, nsCycleCollectionParticipant *aParti) |
|
2175 { |
|
2176 CanonicalizeParticipant(&aRoot, &aParti); |
|
2177 |
|
2178 if (aBuilder.WantAllTraces() || !aParti->CanSkipInCC(aRoot)) { |
|
2179 PtrInfo *pinfo = aBuilder.AddNode(aRoot, aParti); |
|
2180 if (!pinfo) { |
|
2181 return false; |
|
2182 } |
|
2183 } |
|
2184 |
|
2185 return true; |
|
2186 } |
|
2187 |
|
2188 // MayHaveChild() will be false after a Traverse if the object does |
|
2189 // not have any children the CC will visit. |
|
2190 class ChildFinder : public nsCycleCollectionTraversalCallback |
|
2191 { |
|
2192 public: |
|
2193 ChildFinder() : mMayHaveChild(false) {} |
|
2194 |
|
2195 // The logic of the Note*Child functions must mirror that of their |
|
2196 // respective functions in GCGraphBuilder. |
|
2197 NS_IMETHOD_(void) NoteXPCOMChild(nsISupports *child); |
|
2198 NS_IMETHOD_(void) NoteNativeChild(void *child, |
|
2199 nsCycleCollectionParticipant *helper); |
|
2200 NS_IMETHOD_(void) NoteJSChild(void *child); |
|
2201 |
|
2202 NS_IMETHOD_(void) DescribeRefCountedNode(nsrefcnt refcount, |
|
2203 const char *objname) {} |
|
2204 NS_IMETHOD_(void) DescribeGCedNode(bool ismarked, |
|
2205 const char *objname, |
|
2206 uint64_t aCompartmentAddress) {} |
|
2207 NS_IMETHOD_(void) NoteNextEdgeName(const char* name) {} |
|
2208 bool MayHaveChild() { |
|
2209 return mMayHaveChild; |
|
2210 } |
|
2211 private: |
|
2212 bool mMayHaveChild; |
|
2213 }; |
|
2214 |
|
2215 NS_IMETHODIMP_(void) |
|
2216 ChildFinder::NoteXPCOMChild(nsISupports *child) |
|
2217 { |
|
2218 if (!child || !(child = CanonicalizeXPCOMParticipant(child))) |
|
2219 return; |
|
2220 nsXPCOMCycleCollectionParticipant *cp; |
|
2221 ToParticipant(child, &cp); |
|
2222 if (cp && !cp->CanSkip(child, true)) |
|
2223 mMayHaveChild = true; |
|
2224 } |
|
2225 |
|
2226 NS_IMETHODIMP_(void) |
|
2227 ChildFinder::NoteNativeChild(void *child, |
|
2228 nsCycleCollectionParticipant *helper) |
|
2229 { |
|
2230 if (child) |
|
2231 mMayHaveChild = true; |
|
2232 } |
|
2233 |
|
2234 NS_IMETHODIMP_(void) |
|
2235 ChildFinder::NoteJSChild(void *child) |
|
2236 { |
|
2237 if (child && xpc_GCThingIsGrayCCThing(child)) { |
|
2238 mMayHaveChild = true; |
|
2239 } |
|
2240 } |
|
2241 |
|
2242 static bool |
|
2243 MayHaveChild(void *o, nsCycleCollectionParticipant* cp) |
|
2244 { |
|
2245 ChildFinder cf; |
|
2246 cp->Traverse(o, cf); |
|
2247 return cf.MayHaveChild(); |
|
2248 } |
|
2249 |
|
2250 template<class T> |
|
2251 class SegmentedArrayElement : public LinkedListElement<SegmentedArrayElement<T>> |
|
2252 , public AutoFallibleTArray<T, 60> |
|
2253 { |
|
2254 }; |
|
2255 |
|
2256 template<class T> |
|
2257 class SegmentedArray |
|
2258 { |
|
2259 public: |
|
2260 ~SegmentedArray() |
|
2261 { |
|
2262 MOZ_ASSERT(IsEmpty()); |
|
2263 } |
|
2264 |
|
2265 void AppendElement(T& aElement) |
|
2266 { |
|
2267 SegmentedArrayElement<T>* last = mSegments.getLast(); |
|
2268 if (!last || last->Length() == last->Capacity()) { |
|
2269 last = new SegmentedArrayElement<T>(); |
|
2270 mSegments.insertBack(last); |
|
2271 } |
|
2272 last->AppendElement(aElement); |
|
2273 } |
|
2274 |
|
2275 void Clear() |
|
2276 { |
|
2277 SegmentedArrayElement<T>* first; |
|
2278 while ((first = mSegments.popFirst())) { |
|
2279 delete first; |
|
2280 } |
|
2281 } |
|
2282 |
|
2283 SegmentedArrayElement<T>* GetFirstSegment() |
|
2284 { |
|
2285 return mSegments.getFirst(); |
|
2286 } |
|
2287 |
|
2288 bool IsEmpty() |
|
2289 { |
|
2290 return !GetFirstSegment(); |
|
2291 } |
|
2292 |
|
2293 private: |
|
2294 mozilla::LinkedList<SegmentedArrayElement<T>> mSegments; |
|
2295 }; |
|
2296 |
|
2297 // JSPurpleBuffer keeps references to GCThings which might affect the |
|
2298 // next cycle collection. It is owned only by itself and during unlink its |
|
2299 // self reference is broken down and the object ends up killing itself. |
|
2300 // If GC happens before CC, references to GCthings and the self reference are |
|
2301 // removed. |
|
2302 class JSPurpleBuffer |
|
2303 { |
|
2304 public: |
|
2305 JSPurpleBuffer(JSPurpleBuffer*& aReferenceToThis) |
|
2306 : mReferenceToThis(aReferenceToThis) |
|
2307 { |
|
2308 mReferenceToThis = this; |
|
2309 NS_ADDREF_THIS(); |
|
2310 mozilla::HoldJSObjects(this); |
|
2311 } |
|
2312 |
|
2313 ~JSPurpleBuffer() |
|
2314 { |
|
2315 MOZ_ASSERT(mValues.IsEmpty()); |
|
2316 MOZ_ASSERT(mObjects.IsEmpty()); |
|
2317 MOZ_ASSERT(mTenuredObjects.IsEmpty()); |
|
2318 } |
|
2319 |
|
2320 void Destroy() |
|
2321 { |
|
2322 mReferenceToThis = nullptr; |
|
2323 mValues.Clear(); |
|
2324 mObjects.Clear(); |
|
2325 mTenuredObjects.Clear(); |
|
2326 mozilla::DropJSObjects(this); |
|
2327 NS_RELEASE_THIS(); |
|
2328 } |
|
2329 |
|
2330 NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(JSPurpleBuffer) |
|
2331 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(JSPurpleBuffer) |
|
2332 |
|
2333 JSPurpleBuffer*& mReferenceToThis; |
|
2334 SegmentedArray<JS::Heap<JS::Value>> mValues; |
|
2335 SegmentedArray<JS::Heap<JSObject*>> mObjects; |
|
2336 SegmentedArray<JS::TenuredHeap<JSObject*>> mTenuredObjects; |
|
2337 }; |
|
2338 |
|
2339 NS_IMPL_CYCLE_COLLECTION_CLASS(JSPurpleBuffer) |
|
2340 |
|
2341 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSPurpleBuffer) |
|
2342 tmp->Destroy(); |
|
2343 NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
|
2344 |
|
2345 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSPurpleBuffer) |
|
2346 CycleCollectionNoteChild(cb, tmp, "self"); |
|
2347 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS |
|
2348 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
|
2349 |
|
2350 #define NS_TRACE_SEGMENTED_ARRAY(_field) \ |
|
2351 { \ |
|
2352 auto segment = tmp->_field.GetFirstSegment(); \ |
|
2353 while (segment) { \ |
|
2354 for (uint32_t i = segment->Length(); i > 0;) { \ |
|
2355 aCallbacks.Trace(&segment->ElementAt(--i), #_field, aClosure); \ |
|
2356 } \ |
|
2357 segment = segment->getNext(); \ |
|
2358 } \ |
|
2359 } |
|
2360 |
|
2361 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSPurpleBuffer) |
|
2362 NS_TRACE_SEGMENTED_ARRAY(mValues) |
|
2363 NS_TRACE_SEGMENTED_ARRAY(mObjects) |
|
2364 NS_TRACE_SEGMENTED_ARRAY(mTenuredObjects) |
|
2365 NS_IMPL_CYCLE_COLLECTION_TRACE_END |
|
2366 |
|
2367 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(JSPurpleBuffer, AddRef) |
|
2368 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(JSPurpleBuffer, Release) |
|
2369 |
|
2370 struct SnowWhiteObject |
|
2371 { |
|
2372 void* mPointer; |
|
2373 nsCycleCollectionParticipant* mParticipant; |
|
2374 nsCycleCollectingAutoRefCnt* mRefCnt; |
|
2375 }; |
|
2376 |
|
2377 class SnowWhiteKiller : public TraceCallbacks |
|
2378 { |
|
2379 public: |
|
2380 SnowWhiteKiller(nsCycleCollector *aCollector, uint32_t aMaxCount) |
|
2381 : mCollector(aCollector) |
|
2382 { |
|
2383 MOZ_ASSERT(mCollector, "Calling SnowWhiteKiller after nsCC went away"); |
|
2384 while (true) { |
|
2385 if (mObjects.SetCapacity(aMaxCount)) { |
|
2386 break; |
|
2387 } |
|
2388 if (aMaxCount == 1) { |
|
2389 NS_RUNTIMEABORT("Not enough memory to even delete objects!"); |
|
2390 } |
|
2391 aMaxCount /= 2; |
|
2392 } |
|
2393 } |
|
2394 |
|
2395 ~SnowWhiteKiller() |
|
2396 { |
|
2397 for (uint32_t i = 0; i < mObjects.Length(); ++i) { |
|
2398 SnowWhiteObject& o = mObjects[i]; |
|
2399 if (!o.mRefCnt->get() && !o.mRefCnt->IsInPurpleBuffer()) { |
|
2400 mCollector->RemoveObjectFromGraph(o.mPointer); |
|
2401 o.mRefCnt->stabilizeForDeletion(); |
|
2402 o.mParticipant->Trace(o.mPointer, *this, nullptr); |
|
2403 o.mParticipant->DeleteCycleCollectable(o.mPointer); |
|
2404 } |
|
2405 } |
|
2406 } |
|
2407 |
|
2408 void |
|
2409 Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) |
|
2410 { |
|
2411 MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); |
|
2412 if (!aEntry->mRefCnt->get()) { |
|
2413 void *o = aEntry->mObject; |
|
2414 nsCycleCollectionParticipant *cp = aEntry->mParticipant; |
|
2415 CanonicalizeParticipant(&o, &cp); |
|
2416 SnowWhiteObject swo = { o, cp, aEntry->mRefCnt }; |
|
2417 if (mObjects.AppendElement(swo)) { |
|
2418 aBuffer.Remove(aEntry); |
|
2419 } |
|
2420 } |
|
2421 } |
|
2422 |
|
2423 bool HasSnowWhiteObjects() const |
|
2424 { |
|
2425 return mObjects.Length() > 0; |
|
2426 } |
|
2427 |
|
2428 virtual void Trace(JS::Heap<JS::Value>* aValue, const char* aName, |
|
2429 void* aClosure) const |
|
2430 { |
|
2431 if (aValue->isMarkable()) { |
|
2432 void* thing = aValue->toGCThing(); |
|
2433 if (thing && xpc_GCThingIsGrayCCThing(thing)) { |
|
2434 mCollector->GetJSPurpleBuffer()->mValues.AppendElement(*aValue); |
|
2435 } |
|
2436 } |
|
2437 } |
|
2438 |
|
2439 virtual void Trace(JS::Heap<jsid>* aId, const char* aName, |
|
2440 void* aClosure) const |
|
2441 { |
|
2442 } |
|
2443 |
|
2444 virtual void Trace(JS::Heap<JSObject*>* aObject, const char* aName, |
|
2445 void* aClosure) const |
|
2446 { |
|
2447 if (*aObject && xpc_GCThingIsGrayCCThing(*aObject)) { |
|
2448 mCollector->GetJSPurpleBuffer()->mObjects.AppendElement(*aObject); |
|
2449 } |
|
2450 } |
|
2451 |
|
2452 virtual void Trace(JS::TenuredHeap<JSObject*>* aObject, const char* aName, |
|
2453 void* aClosure) const |
|
2454 { |
|
2455 if (*aObject && xpc_GCThingIsGrayCCThing(*aObject)) { |
|
2456 mCollector->GetJSPurpleBuffer()->mTenuredObjects.AppendElement(*aObject); |
|
2457 } |
|
2458 } |
|
2459 |
|
2460 virtual void Trace(JS::Heap<JSString*>* aString, const char* aName, |
|
2461 void* aClosure) const |
|
2462 { |
|
2463 } |
|
2464 |
|
2465 virtual void Trace(JS::Heap<JSScript*>* aScript, const char* aName, |
|
2466 void* aClosure) const |
|
2467 { |
|
2468 } |
|
2469 |
|
2470 virtual void Trace(JS::Heap<JSFunction*>* aFunction, const char* aName, |
|
2471 void* aClosure) const |
|
2472 { |
|
2473 } |
|
2474 |
|
2475 private: |
|
2476 nsCycleCollector *mCollector; |
|
2477 FallibleTArray<SnowWhiteObject> mObjects; |
|
2478 }; |
|
2479 |
|
2480 class RemoveSkippableVisitor : public SnowWhiteKiller |
|
2481 { |
|
2482 public: |
|
2483 RemoveSkippableVisitor(nsCycleCollector* aCollector, |
|
2484 uint32_t aMaxCount, bool aRemoveChildlessNodes, |
|
2485 bool aAsyncSnowWhiteFreeing, |
|
2486 CC_ForgetSkippableCallback aCb) |
|
2487 : SnowWhiteKiller(aCollector, aAsyncSnowWhiteFreeing ? 0 : aMaxCount), |
|
2488 mRemoveChildlessNodes(aRemoveChildlessNodes), |
|
2489 mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing), |
|
2490 mDispatchedDeferredDeletion(false), |
|
2491 mCallback(aCb) |
|
2492 {} |
|
2493 |
|
2494 ~RemoveSkippableVisitor() |
|
2495 { |
|
2496 // Note, we must call the callback before SnowWhiteKiller calls |
|
2497 // DeleteCycleCollectable! |
|
2498 if (mCallback) { |
|
2499 mCallback(); |
|
2500 } |
|
2501 if (HasSnowWhiteObjects()) { |
|
2502 // Effectively a continuation. |
|
2503 nsCycleCollector_dispatchDeferredDeletion(true); |
|
2504 } |
|
2505 } |
|
2506 |
|
2507 void |
|
2508 Visit(nsPurpleBuffer &aBuffer, nsPurpleBufferEntry *aEntry) |
|
2509 { |
|
2510 MOZ_ASSERT(aEntry->mObject, "null mObject in purple buffer"); |
|
2511 if (!aEntry->mRefCnt->get()) { |
|
2512 if (!mAsyncSnowWhiteFreeing) { |
|
2513 SnowWhiteKiller::Visit(aBuffer, aEntry); |
|
2514 } else if (!mDispatchedDeferredDeletion) { |
|
2515 mDispatchedDeferredDeletion = true; |
|
2516 nsCycleCollector_dispatchDeferredDeletion(false); |
|
2517 } |
|
2518 return; |
|
2519 } |
|
2520 void *o = aEntry->mObject; |
|
2521 nsCycleCollectionParticipant *cp = aEntry->mParticipant; |
|
2522 CanonicalizeParticipant(&o, &cp); |
|
2523 if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) && |
|
2524 (!mRemoveChildlessNodes || MayHaveChild(o, cp))) { |
|
2525 return; |
|
2526 } |
|
2527 aBuffer.Remove(aEntry); |
|
2528 } |
|
2529 |
|
2530 private: |
|
2531 bool mRemoveChildlessNodes; |
|
2532 bool mAsyncSnowWhiteFreeing; |
|
2533 bool mDispatchedDeferredDeletion; |
|
2534 CC_ForgetSkippableCallback mCallback; |
|
2535 }; |
|
2536 |
|
2537 void |
|
2538 nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector, |
|
2539 bool aRemoveChildlessNodes, |
|
2540 bool aAsyncSnowWhiteFreeing, |
|
2541 CC_ForgetSkippableCallback aCb) |
|
2542 { |
|
2543 RemoveSkippableVisitor visitor(aCollector, Count(), aRemoveChildlessNodes, |
|
2544 aAsyncSnowWhiteFreeing, aCb); |
|
2545 VisitEntries(visitor); |
|
2546 } |
|
2547 |
|
2548 bool |
|
2549 nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer) |
|
2550 { |
|
2551 CheckThreadSafety(); |
|
2552 |
|
2553 if (mFreeingSnowWhite) { |
|
2554 return false; |
|
2555 } |
|
2556 |
|
2557 AutoRestore<bool> ar(mFreeingSnowWhite); |
|
2558 mFreeingSnowWhite = true; |
|
2559 |
|
2560 bool hadSnowWhiteObjects = false; |
|
2561 do { |
|
2562 SnowWhiteKiller visitor(this, mPurpleBuf.Count()); |
|
2563 mPurpleBuf.VisitEntries(visitor); |
|
2564 hadSnowWhiteObjects = hadSnowWhiteObjects || |
|
2565 visitor.HasSnowWhiteObjects(); |
|
2566 if (!visitor.HasSnowWhiteObjects()) { |
|
2567 break; |
|
2568 } |
|
2569 } while (aUntilNoSWInPurpleBuffer); |
|
2570 return hadSnowWhiteObjects; |
|
2571 } |
|
2572 |
|
2573 void |
|
2574 nsCycleCollector::ForgetSkippable(bool aRemoveChildlessNodes, |
|
2575 bool aAsyncSnowWhiteFreeing) |
|
2576 { |
|
2577 CheckThreadSafety(); |
|
2578 |
|
2579 // If we remove things from the purple buffer during graph building, we may |
|
2580 // lose track of an object that was mutated during graph building. |
|
2581 MOZ_ASSERT(mIncrementalPhase == IdlePhase); |
|
2582 |
|
2583 if (mJSRuntime) { |
|
2584 mJSRuntime->PrepareForForgetSkippable(); |
|
2585 } |
|
2586 MOZ_ASSERT(!mScanInProgress, "Don't forget skippable or free snow-white while scan is in progress."); |
|
2587 mPurpleBuf.RemoveSkippable(this, aRemoveChildlessNodes, |
|
2588 aAsyncSnowWhiteFreeing, mForgetSkippableCB); |
|
2589 } |
|
2590 |
|
2591 MOZ_NEVER_INLINE void |
|
2592 nsCycleCollector::MarkRoots(SliceBudget &aBudget) |
|
2593 { |
|
2594 const intptr_t kNumNodesBetweenTimeChecks = 1000; |
|
2595 const intptr_t kStep = SliceBudget::CounterReset / kNumNodesBetweenTimeChecks; |
|
2596 |
|
2597 TimeLog timeLog; |
|
2598 AutoRestore<bool> ar(mScanInProgress); |
|
2599 MOZ_ASSERT(!mScanInProgress); |
|
2600 mScanInProgress = true; |
|
2601 MOZ_ASSERT(mIncrementalPhase == GraphBuildingPhase); |
|
2602 MOZ_ASSERT(mCurrNode); |
|
2603 |
|
2604 while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) { |
|
2605 PtrInfo *pi = mCurrNode->GetNext(); |
|
2606 if (!pi) { |
|
2607 MOZ_CRASH(); |
|
2608 } |
|
2609 |
|
2610 // We need to call the builder's Traverse() method on deleted nodes, to |
|
2611 // set their firstChild() that may be read by a prior non-deleted |
|
2612 // neighbor. |
|
2613 mBuilder->Traverse(pi); |
|
2614 if (mCurrNode->AtBlockEnd()) { |
|
2615 mBuilder->SetLastChild(); |
|
2616 } |
|
2617 aBudget.step(kStep); |
|
2618 } |
|
2619 |
|
2620 if (!mCurrNode->IsDone()) { |
|
2621 timeLog.Checkpoint("MarkRoots()"); |
|
2622 return; |
|
2623 } |
|
2624 |
|
2625 if (mGraph.mRootCount > 0) { |
|
2626 mBuilder->SetLastChild(); |
|
2627 } |
|
2628 |
|
2629 if (mBuilder->RanOutOfMemory()) { |
|
2630 MOZ_ASSERT(false, "Ran out of memory while building cycle collector graph"); |
|
2631 CC_TELEMETRY(_OOM, true); |
|
2632 } |
|
2633 |
|
2634 mBuilder = nullptr; |
|
2635 mCurrNode = nullptr; |
|
2636 mIncrementalPhase = ScanAndCollectWhitePhase; |
|
2637 timeLog.Checkpoint("MarkRoots()"); |
|
2638 } |
|
2639 |
|
2640 |
|
2641 //////////////////////////////////////////////////////////////////////// |
|
2642 // Bacon & Rajan's |ScanRoots| routine. |
|
2643 //////////////////////////////////////////////////////////////////////// |
|
2644 |
|
2645 |
|
2646 struct ScanBlackVisitor |
|
2647 { |
|
2648 ScanBlackVisitor(uint32_t &aWhiteNodeCount, bool &aFailed) |
|
2649 : mWhiteNodeCount(aWhiteNodeCount), mFailed(aFailed) |
|
2650 { |
|
2651 } |
|
2652 |
|
2653 bool ShouldVisitNode(PtrInfo const *pi) |
|
2654 { |
|
2655 return pi->mColor != black; |
|
2656 } |
|
2657 |
|
2658 MOZ_NEVER_INLINE void VisitNode(PtrInfo *pi) |
|
2659 { |
|
2660 if (pi->mColor == white) |
|
2661 --mWhiteNodeCount; |
|
2662 pi->mColor = black; |
|
2663 } |
|
2664 |
|
2665 void Failed() |
|
2666 { |
|
2667 mFailed = true; |
|
2668 } |
|
2669 |
|
2670 private: |
|
2671 uint32_t &mWhiteNodeCount; |
|
2672 bool &mFailed; |
|
2673 }; |
|
2674 |
|
2675 |
|
2676 struct scanVisitor |
|
2677 { |
|
2678 scanVisitor(uint32_t &aWhiteNodeCount, bool &aFailed, bool aWasIncremental) |
|
2679 : mWhiteNodeCount(aWhiteNodeCount), mFailed(aFailed), |
|
2680 mWasIncremental(aWasIncremental) |
|
2681 { |
|
2682 } |
|
2683 |
|
2684 bool ShouldVisitNode(PtrInfo const *pi) |
|
2685 { |
|
2686 return pi->mColor == grey; |
|
2687 } |
|
2688 |
|
2689 MOZ_NEVER_INLINE void VisitNode(PtrInfo *pi) |
|
2690 { |
|
2691 if (pi->mInternalRefs > pi->mRefCount && pi->mRefCount > 0) { |
|
2692 // If we found more references to an object than its ref count, then |
|
2693 // the object should have already been marked as an incremental |
|
2694 // root. Note that this is imprecise, because pi could have been |
|
2695 // marked black for other reasons. Always fault if we weren't |
|
2696 // incremental, as there were no incremental roots in that case. |
|
2697 if (!mWasIncremental || pi->mColor != black) { |
|
2698 Fault("traversed refs exceed refcount", pi); |
|
2699 } |
|
2700 } |
|
2701 |
|
2702 if (pi->mInternalRefs == pi->mRefCount || pi->mRefCount == 0) { |
|
2703 pi->mColor = white; |
|
2704 ++mWhiteNodeCount; |
|
2705 } else { |
|
2706 GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mWhiteNodeCount, mFailed)).Walk(pi); |
|
2707 MOZ_ASSERT(pi->mColor == black, |
|
2708 "Why didn't ScanBlackVisitor make pi black?"); |
|
2709 } |
|
2710 } |
|
2711 |
|
2712 void Failed() { |
|
2713 mFailed = true; |
|
2714 } |
|
2715 |
|
2716 private: |
|
2717 uint32_t &mWhiteNodeCount; |
|
2718 bool &mFailed; |
|
2719 bool mWasIncremental; |
|
2720 }; |
|
2721 |
|
2722 // Iterate over the WeakMaps. If we mark anything while iterating |
|
2723 // over the WeakMaps, we must iterate over all of the WeakMaps again. |
|
2724 void |
|
2725 nsCycleCollector::ScanWeakMaps() |
|
2726 { |
|
2727 bool anyChanged; |
|
2728 bool failed = false; |
|
2729 do { |
|
2730 anyChanged = false; |
|
2731 for (uint32_t i = 0; i < mGraph.mWeakMaps.Length(); i++) { |
|
2732 WeakMapping *wm = &mGraph.mWeakMaps[i]; |
|
2733 |
|
2734 // If any of these are null, the original object was marked black. |
|
2735 uint32_t mColor = wm->mMap ? wm->mMap->mColor : black; |
|
2736 uint32_t kColor = wm->mKey ? wm->mKey->mColor : black; |
|
2737 uint32_t kdColor = wm->mKeyDelegate ? wm->mKeyDelegate->mColor : black; |
|
2738 uint32_t vColor = wm->mVal ? wm->mVal->mColor : black; |
|
2739 |
|
2740 // All non-null weak mapping maps, keys and values are |
|
2741 // roots (in the sense of WalkFromRoots) in the cycle |
|
2742 // collector graph, and thus should have been colored |
|
2743 // either black or white in ScanRoots(). |
|
2744 MOZ_ASSERT(mColor != grey, "Uncolored weak map"); |
|
2745 MOZ_ASSERT(kColor != grey, "Uncolored weak map key"); |
|
2746 MOZ_ASSERT(kdColor != grey, "Uncolored weak map key delegate"); |
|
2747 MOZ_ASSERT(vColor != grey, "Uncolored weak map value"); |
|
2748 |
|
2749 if (mColor == black && kColor != black && kdColor == black) { |
|
2750 GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mWhiteNodeCount, failed)).Walk(wm->mKey); |
|
2751 anyChanged = true; |
|
2752 } |
|
2753 |
|
2754 if (mColor == black && kColor == black && vColor != black) { |
|
2755 GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mWhiteNodeCount, failed)).Walk(wm->mVal); |
|
2756 anyChanged = true; |
|
2757 } |
|
2758 } |
|
2759 } while (anyChanged); |
|
2760 |
|
2761 if (failed) { |
|
2762 MOZ_ASSERT(false, "Ran out of memory in ScanWeakMaps"); |
|
2763 CC_TELEMETRY(_OOM, true); |
|
2764 } |
|
2765 } |
|
2766 |
|
2767 // Flood black from any objects in the purple buffer that are in the CC graph. |
|
2768 class PurpleScanBlackVisitor |
|
2769 { |
|
2770 public: |
|
2771 PurpleScanBlackVisitor(GCGraph &aGraph, nsICycleCollectorListener *aListener, |
|
2772 uint32_t &aCount, bool &aFailed) |
|
2773 : mGraph(aGraph), mListener(aListener), mCount(aCount), mFailed(aFailed) |
|
2774 { |
|
2775 } |
|
2776 |
|
2777 void |
|
2778 Visit(nsPurpleBuffer &aBuffer, nsPurpleBufferEntry *aEntry) |
|
2779 { |
|
2780 MOZ_ASSERT(aEntry->mObject, "Entries with null mObject shouldn't be in the purple buffer."); |
|
2781 MOZ_ASSERT(aEntry->mRefCnt->get() != 0, "Snow-white objects shouldn't be in the purple buffer."); |
|
2782 |
|
2783 void *obj = aEntry->mObject; |
|
2784 if (!aEntry->mParticipant) { |
|
2785 obj = CanonicalizeXPCOMParticipant(static_cast<nsISupports*>(obj)); |
|
2786 MOZ_ASSERT(obj, "Don't add objects that don't participate in collection!"); |
|
2787 } |
|
2788 |
|
2789 PtrInfo *pi = mGraph.FindNode(obj); |
|
2790 if (!pi) { |
|
2791 return; |
|
2792 } |
|
2793 MOZ_ASSERT(pi->mParticipant, "No dead objects should be in the purple buffer."); |
|
2794 if (MOZ_UNLIKELY(mListener)) { |
|
2795 mListener->NoteIncrementalRoot((uint64_t)pi->mPointer); |
|
2796 } |
|
2797 if (pi->mColor == black) { |
|
2798 return; |
|
2799 } |
|
2800 GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mCount, mFailed)).Walk(pi); |
|
2801 } |
|
2802 |
|
2803 private: |
|
2804 GCGraph &mGraph; |
|
2805 nsICycleCollectorListener *mListener; |
|
2806 uint32_t &mCount; |
|
2807 bool &mFailed; |
|
2808 }; |
|
2809 |
|
2810 // Objects that have been stored somewhere since the start of incremental graph building must |
|
2811 // be treated as live for this cycle collection, because we may not have accurate information |
|
2812 // about who holds references to them. |
|
2813 void |
|
2814 nsCycleCollector::ScanIncrementalRoots() |
|
2815 { |
|
2816 TimeLog timeLog; |
|
2817 |
|
2818 // Reference counted objects: |
|
2819 // We cleared the purple buffer at the start of the current ICC, so if a |
|
2820 // refcounted object is purple, it may have been AddRef'd during the current |
|
2821 // ICC. (It may also have only been released.) If that is the case, we cannot |
|
2822 // be sure that the set of things pointing to the object in the CC graph |
|
2823 // is accurate. Therefore, for safety, we treat any purple objects as being |
|
2824 // live during the current CC. We don't remove anything from the purple |
|
2825 // buffer here, so these objects will be suspected and freed in the next CC |
|
2826 // if they are garbage. |
|
2827 bool failed = false; |
|
2828 PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mListener, mWhiteNodeCount, failed); |
|
2829 mPurpleBuf.VisitEntries(purpleScanBlackVisitor); |
|
2830 timeLog.Checkpoint("ScanIncrementalRoots::fix purple"); |
|
2831 |
|
2832 // Garbage collected objects: |
|
2833 // If a GCed object was added to the graph with a refcount of zero, and is |
|
2834 // now marked black by the GC, it was probably gray before and was exposed |
|
2835 // to active JS, so it may have been stored somewhere, so it needs to be |
|
2836 // treated as live. |
|
2837 if (mJSRuntime) { |
|
2838 nsCycleCollectionParticipant *jsParticipant = mJSRuntime->GCThingParticipant(); |
|
2839 nsCycleCollectionParticipant *zoneParticipant = mJSRuntime->ZoneParticipant(); |
|
2840 NodePool::Enumerator etor(mGraph.mNodes); |
|
2841 |
|
2842 while (!etor.IsDone()) { |
|
2843 PtrInfo *pi = etor.GetNext(); |
|
2844 |
|
2845 // If the refcount is non-zero, pi can't have been a gray JS object. |
|
2846 if (pi->mRefCount != 0) { |
|
2847 continue; |
|
2848 } |
|
2849 |
|
2850 // As an optimization, if an object has already been determined to be live, |
|
2851 // don't consider it further. We can't do this if there is a listener, |
|
2852 // because the listener wants to know the complete set of incremental roots. |
|
2853 if (pi->mColor == black && MOZ_LIKELY(!mListener)) { |
|
2854 continue; |
|
2855 } |
|
2856 |
|
2857 // If the object is still marked gray by the GC, nothing could have gotten |
|
2858 // hold of it, so it isn't an incremental root. |
|
2859 if (pi->mParticipant == jsParticipant) { |
|
2860 if (xpc_GCThingIsGrayCCThing(pi->mPointer)) { |
|
2861 continue; |
|
2862 } |
|
2863 } else if (pi->mParticipant == zoneParticipant) { |
|
2864 JS::Zone *zone = static_cast<JS::Zone*>(pi->mPointer); |
|
2865 if (js::ZoneGlobalsAreAllGray(zone)) { |
|
2866 continue; |
|
2867 } |
|
2868 } else { |
|
2869 MOZ_ASSERT(false, "Non-JS thing with 0 refcount? Treating as live."); |
|
2870 } |
|
2871 |
|
2872 // At this point, pi must be an incremental root. |
|
2873 |
|
2874 // If there's a listener, tell it about this root. We don't bother with the |
|
2875 // optimization of skipping the Walk() if pi is black: it will just return |
|
2876 // without doing anything and there's no need to make this case faster. |
|
2877 if (MOZ_UNLIKELY(mListener)) { |
|
2878 mListener->NoteIncrementalRoot((uint64_t)pi->mPointer); |
|
2879 } |
|
2880 |
|
2881 GraphWalker<ScanBlackVisitor>(ScanBlackVisitor(mWhiteNodeCount, failed)).Walk(pi); |
|
2882 } |
|
2883 |
|
2884 timeLog.Checkpoint("ScanIncrementalRoots::fix JS"); |
|
2885 } |
|
2886 |
|
2887 if (failed) { |
|
2888 NS_ASSERTION(false, "Ran out of memory in ScanIncrementalRoots"); |
|
2889 CC_TELEMETRY(_OOM, true); |
|
2890 } |
|
2891 } |
|
2892 |
|
2893 void |
|
2894 nsCycleCollector::ScanRoots(bool aFullySynchGraphBuild) |
|
2895 { |
|
2896 AutoRestore<bool> ar(mScanInProgress); |
|
2897 MOZ_ASSERT(!mScanInProgress); |
|
2898 mScanInProgress = true; |
|
2899 mWhiteNodeCount = 0; |
|
2900 MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase); |
|
2901 |
|
2902 if (!aFullySynchGraphBuild) { |
|
2903 ScanIncrementalRoots(); |
|
2904 } |
|
2905 |
|
2906 TimeLog timeLog; |
|
2907 |
|
2908 // On the assumption that most nodes will be black, it's |
|
2909 // probably faster to use a GraphWalker than a |
|
2910 // NodePool::Enumerator. |
|
2911 bool failed = false; |
|
2912 scanVisitor sv(mWhiteNodeCount, failed, !aFullySynchGraphBuild); |
|
2913 GraphWalker<scanVisitor>(sv).WalkFromRoots(mGraph); |
|
2914 timeLog.Checkpoint("ScanRoots::WalkFromRoots"); |
|
2915 |
|
2916 if (failed) { |
|
2917 NS_ASSERTION(false, "Ran out of memory in ScanRoots"); |
|
2918 CC_TELEMETRY(_OOM, true); |
|
2919 } |
|
2920 |
|
2921 // Scanning weak maps must be done last. |
|
2922 ScanWeakMaps(); |
|
2923 timeLog.Checkpoint("ScanRoots::ScanWeakMaps"); |
|
2924 |
|
2925 if (mListener) { |
|
2926 mListener->BeginResults(); |
|
2927 |
|
2928 NodePool::Enumerator etor(mGraph.mNodes); |
|
2929 while (!etor.IsDone()) { |
|
2930 PtrInfo *pi = etor.GetNext(); |
|
2931 if (!pi->mParticipant) { |
|
2932 continue; |
|
2933 } |
|
2934 switch (pi->mColor) { |
|
2935 case black: |
|
2936 if (pi->mRefCount > 0 && pi->mRefCount < UINT32_MAX && |
|
2937 pi->mInternalRefs != pi->mRefCount) { |
|
2938 mListener->DescribeRoot((uint64_t)pi->mPointer, |
|
2939 pi->mInternalRefs); |
|
2940 } |
|
2941 break; |
|
2942 case white: |
|
2943 mListener->DescribeGarbage((uint64_t)pi->mPointer); |
|
2944 break; |
|
2945 case grey: |
|
2946 // With incremental CC, we can end up with a grey object after |
|
2947 // scanning if it is only reachable from an object that gets freed. |
|
2948 break; |
|
2949 } |
|
2950 } |
|
2951 |
|
2952 mListener->End(); |
|
2953 mListener = nullptr; |
|
2954 timeLog.Checkpoint("ScanRoots::listener"); |
|
2955 } |
|
2956 } |
|
2957 |
|
2958 |
|
2959 //////////////////////////////////////////////////////////////////////// |
|
2960 // Bacon & Rajan's |CollectWhite| routine, somewhat modified. |
|
2961 //////////////////////////////////////////////////////////////////////// |
|
2962 |
|
2963 bool |
|
2964 nsCycleCollector::CollectWhite() |
|
2965 { |
|
2966 // Explanation of "somewhat modified": we have no way to collect the |
|
2967 // set of whites "all at once", we have to ask each of them to drop |
|
2968 // their outgoing links and assume this will cause the garbage cycle |
|
2969 // to *mostly* self-destruct (except for the reference we continue |
|
2970 // to hold). |
|
2971 // |
|
2972 // To do this "safely" we must make sure that the white nodes we're |
|
2973 // operating on are stable for the duration of our operation. So we |
|
2974 // make 3 sets of calls to language runtimes: |
|
2975 // |
|
2976 // - Root(whites), which should pin the whites in memory. |
|
2977 // - Unlink(whites), which drops outgoing links on each white. |
|
2978 // - Unroot(whites), which returns the whites to normal GC. |
|
2979 |
|
2980 TimeLog timeLog; |
|
2981 nsAutoTArray<PtrInfo*, 4000> whiteNodes; |
|
2982 |
|
2983 MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase); |
|
2984 |
|
2985 whiteNodes.SetCapacity(mWhiteNodeCount); |
|
2986 uint32_t numWhiteGCed = 0; |
|
2987 |
|
2988 NodePool::Enumerator etor(mGraph.mNodes); |
|
2989 while (!etor.IsDone()) |
|
2990 { |
|
2991 PtrInfo *pinfo = etor.GetNext(); |
|
2992 if (pinfo->mColor == white && pinfo->mParticipant) { |
|
2993 whiteNodes.AppendElement(pinfo); |
|
2994 pinfo->mParticipant->Root(pinfo->mPointer); |
|
2995 if (pinfo->mRefCount == 0) { |
|
2996 // only JS objects have a refcount of 0 |
|
2997 ++numWhiteGCed; |
|
2998 } |
|
2999 } |
|
3000 } |
|
3001 |
|
3002 uint32_t count = whiteNodes.Length(); |
|
3003 MOZ_ASSERT(numWhiteGCed <= count, |
|
3004 "More freed GCed nodes than total freed nodes."); |
|
3005 mResults.mFreedRefCounted += count - numWhiteGCed; |
|
3006 mResults.mFreedGCed += numWhiteGCed; |
|
3007 |
|
3008 timeLog.Checkpoint("CollectWhite::Root"); |
|
3009 |
|
3010 if (mBeforeUnlinkCB) { |
|
3011 mBeforeUnlinkCB(); |
|
3012 timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB"); |
|
3013 } |
|
3014 |
|
3015 for (uint32_t i = 0; i < count; ++i) { |
|
3016 PtrInfo *pinfo = whiteNodes.ElementAt(i); |
|
3017 MOZ_ASSERT(pinfo->mParticipant, "Unlink shouldn't see objects removed from graph."); |
|
3018 pinfo->mParticipant->Unlink(pinfo->mPointer); |
|
3019 #ifdef DEBUG |
|
3020 if (mJSRuntime) { |
|
3021 mJSRuntime->AssertNoObjectsToTrace(pinfo->mPointer); |
|
3022 } |
|
3023 #endif |
|
3024 } |
|
3025 timeLog.Checkpoint("CollectWhite::Unlink"); |
|
3026 |
|
3027 for (uint32_t i = 0; i < count; ++i) { |
|
3028 PtrInfo *pinfo = whiteNodes.ElementAt(i); |
|
3029 MOZ_ASSERT(pinfo->mParticipant, "Unroot shouldn't see objects removed from graph."); |
|
3030 pinfo->mParticipant->Unroot(pinfo->mPointer); |
|
3031 } |
|
3032 timeLog.Checkpoint("CollectWhite::Unroot"); |
|
3033 |
|
3034 nsCycleCollector_dispatchDeferredDeletion(false); |
|
3035 mIncrementalPhase = CleanupPhase; |
|
3036 |
|
3037 return count > 0; |
|
3038 } |
|
3039 |
|
3040 |
|
3041 //////////////////////// |
|
3042 // Memory reporting |
|
3043 //////////////////////// |
|
3044 |
|
3045 MOZ_DEFINE_MALLOC_SIZE_OF(CycleCollectorMallocSizeOf) |
|
3046 |
|
3047 NS_IMETHODIMP |
|
3048 nsCycleCollector::CollectReports(nsIHandleReportCallback* aHandleReport, |
|
3049 nsISupports* aData) |
|
3050 { |
|
3051 size_t objectSize, graphNodesSize, graphEdgesSize, weakMapsSize, |
|
3052 purpleBufferSize; |
|
3053 SizeOfIncludingThis(CycleCollectorMallocSizeOf, |
|
3054 &objectSize, |
|
3055 &graphNodesSize, &graphEdgesSize, |
|
3056 &weakMapsSize, |
|
3057 &purpleBufferSize); |
|
3058 |
|
3059 #define REPORT(_path, _amount, _desc) \ |
|
3060 do { \ |
|
3061 size_t amount = _amount; /* evaluate |_amount| only once */ \ |
|
3062 if (amount > 0) { \ |
|
3063 nsresult rv; \ |
|
3064 rv = aHandleReport->Callback(EmptyCString(), \ |
|
3065 NS_LITERAL_CSTRING(_path), \ |
|
3066 KIND_HEAP, UNITS_BYTES, _amount, \ |
|
3067 NS_LITERAL_CSTRING(_desc), \ |
|
3068 aData); \ |
|
3069 if (NS_WARN_IF(NS_FAILED(rv))) \ |
|
3070 return rv; \ |
|
3071 } \ |
|
3072 } while (0) |
|
3073 |
|
3074 REPORT("explicit/cycle-collector/collector-object", objectSize, |
|
3075 "Memory used for the cycle collector object itself."); |
|
3076 |
|
3077 REPORT("explicit/cycle-collector/graph-nodes", graphNodesSize, |
|
3078 "Memory used for the nodes of the cycle collector's graph. " |
|
3079 "This should be zero when the collector is idle."); |
|
3080 |
|
3081 REPORT("explicit/cycle-collector/graph-edges", graphEdgesSize, |
|
3082 "Memory used for the edges of the cycle collector's graph. " |
|
3083 "This should be zero when the collector is idle."); |
|
3084 |
|
3085 REPORT("explicit/cycle-collector/weak-maps", weakMapsSize, |
|
3086 "Memory used for the representation of weak maps in the " |
|
3087 "cycle collector's graph. " |
|
3088 "This should be zero when the collector is idle."); |
|
3089 |
|
3090 REPORT("explicit/cycle-collector/purple-buffer", purpleBufferSize, |
|
3091 "Memory used for the cycle collector's purple buffer."); |
|
3092 |
|
3093 #undef REPORT |
|
3094 |
|
3095 return NS_OK; |
|
3096 }; |
|
3097 |
|
3098 |
|
3099 //////////////////////////////////////////////////////////////////////// |
|
3100 // Collector implementation |
|
3101 //////////////////////////////////////////////////////////////////////// |
|
3102 |
|
3103 nsCycleCollector::nsCycleCollector() : |
|
3104 mActivelyCollecting(false), |
|
3105 mFreeingSnowWhite(false), |
|
3106 mScanInProgress(false), |
|
3107 mJSRuntime(nullptr), |
|
3108 mIncrementalPhase(IdlePhase), |
|
3109 mThread(NS_GetCurrentThread()), |
|
3110 mWhiteNodeCount(0), |
|
3111 mBeforeUnlinkCB(nullptr), |
|
3112 mForgetSkippableCB(nullptr), |
|
3113 mUnmergedNeeded(0), |
|
3114 mMergedInARow(0), |
|
3115 mJSPurpleBuffer(nullptr) |
|
3116 { |
|
3117 } |
|
3118 |
|
3119 nsCycleCollector::~nsCycleCollector() |
|
3120 { |
|
3121 UnregisterWeakMemoryReporter(this); |
|
3122 } |
|
3123 |
|
3124 void |
|
3125 nsCycleCollector::RegisterJSRuntime(CycleCollectedJSRuntime *aJSRuntime) |
|
3126 { |
|
3127 if (mJSRuntime) |
|
3128 Fault("multiple registrations of cycle collector JS runtime", aJSRuntime); |
|
3129 |
|
3130 mJSRuntime = aJSRuntime; |
|
3131 |
|
3132 // We can't register as a reporter in nsCycleCollector() because that runs |
|
3133 // before the memory reporter manager is initialized. So we do it here |
|
3134 // instead. |
|
3135 static bool registered = false; |
|
3136 if (!registered) { |
|
3137 RegisterWeakMemoryReporter(this); |
|
3138 registered = true; |
|
3139 } |
|
3140 } |
|
3141 |
|
3142 void |
|
3143 nsCycleCollector::ForgetJSRuntime() |
|
3144 { |
|
3145 if (!mJSRuntime) |
|
3146 Fault("forgetting non-registered cycle collector JS runtime"); |
|
3147 |
|
3148 mJSRuntime = nullptr; |
|
3149 } |
|
3150 |
|
3151 #ifdef DEBUG |
|
3152 static bool |
|
3153 HasParticipant(void *aPtr, nsCycleCollectionParticipant *aParti) |
|
3154 { |
|
3155 if (aParti) { |
|
3156 return true; |
|
3157 } |
|
3158 |
|
3159 nsXPCOMCycleCollectionParticipant *xcp; |
|
3160 ToParticipant(static_cast<nsISupports*>(aPtr), &xcp); |
|
3161 return xcp != nullptr; |
|
3162 } |
|
3163 #endif |
|
3164 |
|
3165 MOZ_ALWAYS_INLINE void |
|
3166 nsCycleCollector::Suspect(void *aPtr, nsCycleCollectionParticipant *aParti, |
|
3167 nsCycleCollectingAutoRefCnt *aRefCnt) |
|
3168 { |
|
3169 CheckThreadSafety(); |
|
3170 |
|
3171 // Re-entering ::Suspect during collection used to be a fault, but |
|
3172 // we are canonicalizing nsISupports pointers using QI, so we will |
|
3173 // see some spurious refcount traffic here. |
|
3174 |
|
3175 if (MOZ_UNLIKELY(mScanInProgress)) { |
|
3176 return; |
|
3177 } |
|
3178 |
|
3179 MOZ_ASSERT(aPtr, "Don't suspect null pointers"); |
|
3180 |
|
3181 MOZ_ASSERT(HasParticipant(aPtr, aParti), |
|
3182 "Suspected nsISupports pointer must QI to nsXPCOMCycleCollectionParticipant"); |
|
3183 |
|
3184 mPurpleBuf.Put(aPtr, aParti, aRefCnt); |
|
3185 } |
|
3186 |
|
3187 void |
|
3188 nsCycleCollector::CheckThreadSafety() |
|
3189 { |
|
3190 #ifdef DEBUG |
|
3191 nsIThread* currentThread = NS_GetCurrentThread(); |
|
3192 // XXXkhuey we can be called so late in shutdown that NS_GetCurrentThread |
|
3193 // returns null (after the thread manager has shut down) |
|
3194 MOZ_ASSERT(mThread == currentThread || !currentThread); |
|
3195 #endif |
|
3196 } |
|
3197 |
|
3198 // The cycle collector uses the mark bitmap to discover what JS objects |
|
3199 // were reachable only from XPConnect roots that might participate in |
|
3200 // cycles. We ask the JS runtime whether we need to force a GC before |
|
3201 // this CC. It returns true on startup (before the mark bits have been set), |
|
3202 // and also when UnmarkGray has run out of stack. We also force GCs on shut |
|
3203 // down to collect cycles involving both DOM and JS. |
|
3204 void |
|
3205 nsCycleCollector::FixGrayBits(bool aForceGC) |
|
3206 { |
|
3207 CheckThreadSafety(); |
|
3208 |
|
3209 if (!mJSRuntime) |
|
3210 return; |
|
3211 |
|
3212 if (!aForceGC) { |
|
3213 mJSRuntime->FixWeakMappingGrayBits(); |
|
3214 |
|
3215 bool needGC = mJSRuntime->NeedCollect(); |
|
3216 // Only do a telemetry ping for non-shutdown CCs. |
|
3217 CC_TELEMETRY(_NEED_GC, needGC); |
|
3218 if (!needGC) |
|
3219 return; |
|
3220 mResults.mForcedGC = true; |
|
3221 } |
|
3222 |
|
3223 TimeLog timeLog; |
|
3224 mJSRuntime->Collect(aForceGC ? JS::gcreason::SHUTDOWN_CC : JS::gcreason::CC_FORCED); |
|
3225 timeLog.Checkpoint("GC()"); |
|
3226 } |
|
3227 |
|
3228 void |
|
3229 nsCycleCollector::CleanupAfterCollection() |
|
3230 { |
|
3231 MOZ_ASSERT(mIncrementalPhase == CleanupPhase); |
|
3232 mGraph.Clear(); |
|
3233 |
|
3234 uint32_t interval = (uint32_t) ((TimeStamp::Now() - mCollectionStart).ToMilliseconds()); |
|
3235 #ifdef COLLECT_TIME_DEBUG |
|
3236 printf("cc: total cycle collector time was %ums\n", interval); |
|
3237 printf("cc: visited %u ref counted and %u GCed objects, freed %d ref counted and %d GCed objects", |
|
3238 mResults.mVisitedRefCounted, mResults.mVisitedGCed, |
|
3239 mResults.mFreedRefCounted, mResults.mFreedGCed); |
|
3240 uint32_t numVisited = mResults.mVisitedRefCounted + mResults.mVisitedGCed; |
|
3241 if (numVisited > 1000) { |
|
3242 uint32_t numFreed = mResults.mFreedRefCounted + mResults.mFreedGCed; |
|
3243 printf(" (%d%%)", 100 * numFreed / numVisited); |
|
3244 } |
|
3245 printf(".\ncc: \n"); |
|
3246 #endif |
|
3247 CC_TELEMETRY( , interval); |
|
3248 CC_TELEMETRY(_VISITED_REF_COUNTED, mResults.mVisitedRefCounted); |
|
3249 CC_TELEMETRY(_VISITED_GCED, mResults.mVisitedGCed); |
|
3250 CC_TELEMETRY(_COLLECTED, mWhiteNodeCount); |
|
3251 |
|
3252 if (mJSRuntime) { |
|
3253 mJSRuntime->EndCycleCollectionCallback(mResults); |
|
3254 } |
|
3255 mIncrementalPhase = IdlePhase; |
|
3256 } |
|
3257 |
|
3258 void |
|
3259 nsCycleCollector::ShutdownCollect() |
|
3260 { |
|
3261 SliceBudget unlimitedBudget; |
|
3262 uint32_t i; |
|
3263 for (i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS; ++i) { |
|
3264 if (!Collect(ShutdownCC, unlimitedBudget, nullptr)) { |
|
3265 break; |
|
3266 } |
|
3267 } |
|
3268 NS_WARN_IF_FALSE(i < NORMAL_SHUTDOWN_COLLECTIONS, "Extra shutdown CC"); |
|
3269 } |
|
3270 |
|
3271 static void |
|
3272 PrintPhase(const char *aPhase) |
|
3273 { |
|
3274 #ifdef DEBUG_PHASES |
|
3275 printf("cc: begin %s on %s\n", aPhase, |
|
3276 NS_IsMainThread() ? "mainthread" : "worker"); |
|
3277 #endif |
|
3278 } |
|
3279 |
|
3280 bool |
|
3281 nsCycleCollector::Collect(ccType aCCType, |
|
3282 SliceBudget &aBudget, |
|
3283 nsICycleCollectorListener *aManualListener) |
|
3284 { |
|
3285 CheckThreadSafety(); |
|
3286 |
|
3287 // This can legitimately happen in a few cases. See bug 383651. |
|
3288 if (mActivelyCollecting || mFreeingSnowWhite) { |
|
3289 return false; |
|
3290 } |
|
3291 mActivelyCollecting = true; |
|
3292 |
|
3293 bool startedIdle = (mIncrementalPhase == IdlePhase); |
|
3294 bool collectedAny = false; |
|
3295 |
|
3296 // If the CC started idle, it will call BeginCollection, which |
|
3297 // will do FreeSnowWhite, so it doesn't need to be done here. |
|
3298 if (!startedIdle) { |
|
3299 FreeSnowWhite(true); |
|
3300 } |
|
3301 |
|
3302 bool finished = false; |
|
3303 do { |
|
3304 switch (mIncrementalPhase) { |
|
3305 case IdlePhase: |
|
3306 PrintPhase("BeginCollection"); |
|
3307 BeginCollection(aCCType, aManualListener); |
|
3308 break; |
|
3309 case GraphBuildingPhase: |
|
3310 PrintPhase("MarkRoots"); |
|
3311 MarkRoots(aBudget); |
|
3312 break; |
|
3313 case ScanAndCollectWhitePhase: |
|
3314 // We do ScanRoots and CollectWhite in a single slice to ensure |
|
3315 // that we won't unlink a live object if a weak reference is |
|
3316 // promoted to a strong reference after ScanRoots has finished. |
|
3317 // See bug 926533. |
|
3318 PrintPhase("ScanRoots"); |
|
3319 ScanRoots(startedIdle); |
|
3320 PrintPhase("CollectWhite"); |
|
3321 collectedAny = CollectWhite(); |
|
3322 break; |
|
3323 case CleanupPhase: |
|
3324 PrintPhase("CleanupAfterCollection"); |
|
3325 CleanupAfterCollection(); |
|
3326 finished = true; |
|
3327 break; |
|
3328 } |
|
3329 } while (!aBudget.checkOverBudget() && !finished); |
|
3330 |
|
3331 // Clear mActivelyCollecting here to ensure that a recursive call to |
|
3332 // Collect() does something. |
|
3333 mActivelyCollecting = false; |
|
3334 |
|
3335 if (aCCType != SliceCC && !startedIdle) { |
|
3336 // We were in the middle of an incremental CC (using its own listener). |
|
3337 // Somebody has forced a CC, so after having finished out the current CC, |
|
3338 // run the CC again using the new listener. |
|
3339 MOZ_ASSERT(mIncrementalPhase == IdlePhase); |
|
3340 if (Collect(aCCType, aBudget, aManualListener)) { |
|
3341 collectedAny = true; |
|
3342 } |
|
3343 } |
|
3344 |
|
3345 MOZ_ASSERT_IF(aCCType != SliceCC, mIncrementalPhase == IdlePhase); |
|
3346 |
|
3347 return collectedAny; |
|
3348 } |
|
3349 |
|
3350 // Any JS objects we have in the graph could die when we GC, but we |
|
3351 // don't want to abandon the current CC, because the graph contains |
|
3352 // information about purple roots. So we synchronously finish off |
|
3353 // the current CC. |
|
3354 void |
|
3355 nsCycleCollector::PrepareForGarbageCollection() |
|
3356 { |
|
3357 if (mIncrementalPhase == IdlePhase) { |
|
3358 MOZ_ASSERT(mGraph.IsEmpty(), "Non-empty graph when idle"); |
|
3359 MOZ_ASSERT(!mBuilder, "Non-null builder when idle"); |
|
3360 if (mJSPurpleBuffer) { |
|
3361 mJSPurpleBuffer->Destroy(); |
|
3362 } |
|
3363 return; |
|
3364 } |
|
3365 |
|
3366 SliceBudget unlimitedBudget; |
|
3367 PrintPhase("PrepareForGarbageCollection"); |
|
3368 // Use SliceCC because we only want to finish the CC in progress. |
|
3369 Collect(SliceCC, unlimitedBudget, nullptr); |
|
3370 MOZ_ASSERT(mIncrementalPhase == IdlePhase); |
|
3371 } |
|
3372 |
|
3373 // Don't merge too many times in a row, and do at least a minimum |
|
3374 // number of unmerged CCs in a row. |
|
3375 static const uint32_t kMinConsecutiveUnmerged = 3; |
|
3376 static const uint32_t kMaxConsecutiveMerged = 3; |
|
3377 |
|
3378 bool |
|
3379 nsCycleCollector::ShouldMergeZones(ccType aCCType) |
|
3380 { |
|
3381 if (!mJSRuntime) { |
|
3382 return false; |
|
3383 } |
|
3384 |
|
3385 MOZ_ASSERT(mUnmergedNeeded <= kMinConsecutiveUnmerged); |
|
3386 MOZ_ASSERT(mMergedInARow <= kMaxConsecutiveMerged); |
|
3387 |
|
3388 if (mMergedInARow == kMaxConsecutiveMerged) { |
|
3389 MOZ_ASSERT(mUnmergedNeeded == 0); |
|
3390 mUnmergedNeeded = kMinConsecutiveUnmerged; |
|
3391 } |
|
3392 |
|
3393 if (mUnmergedNeeded > 0) { |
|
3394 mUnmergedNeeded--; |
|
3395 mMergedInARow = 0; |
|
3396 return false; |
|
3397 } |
|
3398 |
|
3399 if (aCCType == SliceCC && mJSRuntime->UsefulToMergeZones()) { |
|
3400 mMergedInARow++; |
|
3401 return true; |
|
3402 } else { |
|
3403 mMergedInARow = 0; |
|
3404 return false; |
|
3405 } |
|
3406 } |
|
3407 |
|
3408 void |
|
3409 nsCycleCollector::BeginCollection(ccType aCCType, |
|
3410 nsICycleCollectorListener *aManualListener) |
|
3411 { |
|
3412 TimeLog timeLog; |
|
3413 MOZ_ASSERT(mIncrementalPhase == IdlePhase); |
|
3414 |
|
3415 mCollectionStart = TimeStamp::Now(); |
|
3416 |
|
3417 if (mJSRuntime) { |
|
3418 mJSRuntime->BeginCycleCollectionCallback(); |
|
3419 timeLog.Checkpoint("BeginCycleCollectionCallback()"); |
|
3420 } |
|
3421 |
|
3422 bool isShutdown = (aCCType == ShutdownCC); |
|
3423 |
|
3424 // Set up the listener for this CC. |
|
3425 MOZ_ASSERT_IF(isShutdown, !aManualListener); |
|
3426 MOZ_ASSERT(!mListener, "Forgot to clear a previous listener?"); |
|
3427 mListener = aManualListener; |
|
3428 aManualListener = nullptr; |
|
3429 if (!mListener && mParams.LogThisCC(isShutdown)) { |
|
3430 nsRefPtr<nsCycleCollectorLogger> logger = new nsCycleCollectorLogger(); |
|
3431 if (mParams.AllTracesThisCC(isShutdown)) { |
|
3432 logger->SetAllTraces(); |
|
3433 } |
|
3434 mListener = logger.forget(); |
|
3435 } |
|
3436 |
|
3437 bool forceGC = isShutdown; |
|
3438 if (!forceGC && mListener) { |
|
3439 // On a WantAllTraces CC, force a synchronous global GC to prevent |
|
3440 // hijinks from ForgetSkippable and compartmental GCs. |
|
3441 mListener->GetWantAllTraces(&forceGC); |
|
3442 } |
|
3443 FixGrayBits(forceGC); |
|
3444 |
|
3445 FreeSnowWhite(true); |
|
3446 |
|
3447 if (mListener && NS_FAILED(mListener->Begin())) { |
|
3448 mListener = nullptr; |
|
3449 } |
|
3450 |
|
3451 // Set up the data structures for building the graph. |
|
3452 mGraph.Init(); |
|
3453 mResults.Init(); |
|
3454 bool mergeZones = ShouldMergeZones(aCCType); |
|
3455 mResults.mMergedZones = mergeZones; |
|
3456 |
|
3457 MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder"); |
|
3458 mBuilder = new GCGraphBuilder(mGraph, mResults, mJSRuntime, mListener, mergeZones); |
|
3459 |
|
3460 if (mJSRuntime) { |
|
3461 mJSRuntime->TraverseRoots(*mBuilder); |
|
3462 timeLog.Checkpoint("mJSRuntime->TraverseRoots()"); |
|
3463 } |
|
3464 |
|
3465 AutoRestore<bool> ar(mScanInProgress); |
|
3466 MOZ_ASSERT(!mScanInProgress); |
|
3467 mScanInProgress = true; |
|
3468 mPurpleBuf.SelectPointers(*mBuilder); |
|
3469 timeLog.Checkpoint("SelectPointers()"); |
|
3470 |
|
3471 // We've finished adding roots, and everything in the graph is a root. |
|
3472 mGraph.mRootCount = mGraph.MapCount(); |
|
3473 |
|
3474 mCurrNode = new NodePool::Enumerator(mGraph.mNodes); |
|
3475 mIncrementalPhase = GraphBuildingPhase; |
|
3476 } |
|
3477 |
|
3478 uint32_t |
|
3479 nsCycleCollector::SuspectedCount() |
|
3480 { |
|
3481 CheckThreadSafety(); |
|
3482 return mPurpleBuf.Count(); |
|
3483 } |
|
3484 |
|
3485 void |
|
3486 nsCycleCollector::Shutdown() |
|
3487 { |
|
3488 CheckThreadSafety(); |
|
3489 |
|
3490 // Always delete snow white objects. |
|
3491 FreeSnowWhite(true); |
|
3492 |
|
3493 #ifndef DEBUG |
|
3494 if (PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN")) |
|
3495 #endif |
|
3496 { |
|
3497 ShutdownCollect(); |
|
3498 } |
|
3499 } |
|
3500 |
|
3501 void |
|
3502 nsCycleCollector::RemoveObjectFromGraph(void *aObj) |
|
3503 { |
|
3504 if (mIncrementalPhase == IdlePhase) { |
|
3505 return; |
|
3506 } |
|
3507 |
|
3508 if (PtrInfo *pinfo = mGraph.FindNode(aObj)) { |
|
3509 mGraph.RemoveNodeFromMap(aObj); |
|
3510 |
|
3511 pinfo->mPointer = nullptr; |
|
3512 pinfo->mParticipant = nullptr; |
|
3513 } |
|
3514 } |
|
3515 |
|
3516 void |
|
3517 nsCycleCollector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, |
|
3518 size_t *aObjectSize, |
|
3519 size_t *aGraphNodesSize, |
|
3520 size_t *aGraphEdgesSize, |
|
3521 size_t *aWeakMapsSize, |
|
3522 size_t *aPurpleBufferSize) const |
|
3523 { |
|
3524 *aObjectSize = aMallocSizeOf(this); |
|
3525 |
|
3526 mGraph.SizeOfExcludingThis(aMallocSizeOf, aGraphNodesSize, aGraphEdgesSize, |
|
3527 aWeakMapsSize); |
|
3528 |
|
3529 *aPurpleBufferSize = mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf); |
|
3530 |
|
3531 // These fields are deliberately not measured: |
|
3532 // - mJSRuntime: because it's non-owning and measured by JS reporters. |
|
3533 // - mParams: because it only contains scalars. |
|
3534 } |
|
3535 |
|
3536 JSPurpleBuffer* |
|
3537 nsCycleCollector::GetJSPurpleBuffer() |
|
3538 { |
|
3539 if (!mJSPurpleBuffer) { |
|
3540 // JSPurpleBuffer keeps itself alive, but we need to create it in such way |
|
3541 // that it ends up in the normal purple buffer. That happens when |
|
3542 // nsRefPtr goes out of the scope and calls Release. |
|
3543 nsRefPtr<JSPurpleBuffer> pb = new JSPurpleBuffer(mJSPurpleBuffer); |
|
3544 } |
|
3545 return mJSPurpleBuffer; |
|
3546 } |
|
3547 |
|
3548 //////////////////////////////////////////////////////////////////////// |
|
3549 // Module public API (exported in nsCycleCollector.h) |
|
3550 // Just functions that redirect into the singleton, once it's built. |
|
3551 //////////////////////////////////////////////////////////////////////// |
|
3552 |
|
3553 void |
|
3554 nsCycleCollector_registerJSRuntime(CycleCollectedJSRuntime *rt) |
|
3555 { |
|
3556 CollectorData *data = sCollectorData.get(); |
|
3557 |
|
3558 // We should have started the cycle collector by now. |
|
3559 MOZ_ASSERT(data); |
|
3560 MOZ_ASSERT(data->mCollector); |
|
3561 // But we shouldn't already have a runtime. |
|
3562 MOZ_ASSERT(!data->mRuntime); |
|
3563 |
|
3564 data->mRuntime = rt; |
|
3565 data->mCollector->RegisterJSRuntime(rt); |
|
3566 } |
|
3567 |
|
3568 void |
|
3569 nsCycleCollector_forgetJSRuntime() |
|
3570 { |
|
3571 CollectorData *data = sCollectorData.get(); |
|
3572 |
|
3573 // We should have started the cycle collector by now. |
|
3574 MOZ_ASSERT(data); |
|
3575 // And we shouldn't have already forgotten our runtime. |
|
3576 MOZ_ASSERT(data->mRuntime); |
|
3577 |
|
3578 // But it may have shutdown already. |
|
3579 if (data->mCollector) { |
|
3580 data->mCollector->ForgetJSRuntime(); |
|
3581 data->mRuntime = nullptr; |
|
3582 } else { |
|
3583 data->mRuntime = nullptr; |
|
3584 delete data; |
|
3585 sCollectorData.set(nullptr); |
|
3586 } |
|
3587 } |
|
3588 |
|
3589 /* static */ CycleCollectedJSRuntime* |
|
3590 CycleCollectedJSRuntime::Get() |
|
3591 { |
|
3592 CollectorData* data = sCollectorData.get(); |
|
3593 if (data) { |
|
3594 return data->mRuntime; |
|
3595 } |
|
3596 return nullptr; |
|
3597 } |
|
3598 |
|
3599 |
|
3600 namespace mozilla { |
|
3601 namespace cyclecollector { |
|
3602 |
|
3603 void |
|
3604 HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer) |
|
3605 { |
|
3606 CollectorData* data = sCollectorData.get(); |
|
3607 |
|
3608 // We should have started the cycle collector by now. |
|
3609 MOZ_ASSERT(data); |
|
3610 MOZ_ASSERT(data->mCollector); |
|
3611 // And we should have a runtime. |
|
3612 MOZ_ASSERT(data->mRuntime); |
|
3613 |
|
3614 data->mRuntime->AddJSHolder(aHolder, aTracer); |
|
3615 } |
|
3616 |
|
3617 void |
|
3618 HoldJSObjectsImpl(nsISupports* aHolder) |
|
3619 { |
|
3620 nsXPCOMCycleCollectionParticipant* participant; |
|
3621 CallQueryInterface(aHolder, &participant); |
|
3622 MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!"); |
|
3623 MOZ_ASSERT(participant->CheckForRightISupports(aHolder), |
|
3624 "The result of QIing a JS holder should be the same as ToSupports"); |
|
3625 |
|
3626 HoldJSObjectsImpl(aHolder, participant); |
|
3627 } |
|
3628 |
|
3629 void |
|
3630 DropJSObjectsImpl(void* aHolder) |
|
3631 { |
|
3632 CollectorData* data = sCollectorData.get(); |
|
3633 |
|
3634 // We should have started the cycle collector by now, and not completely |
|
3635 // shut down. |
|
3636 MOZ_ASSERT(data); |
|
3637 // And we should have a runtime. |
|
3638 MOZ_ASSERT(data->mRuntime); |
|
3639 |
|
3640 data->mRuntime->RemoveJSHolder(aHolder); |
|
3641 } |
|
3642 |
|
3643 void |
|
3644 DropJSObjectsImpl(nsISupports* aHolder) |
|
3645 { |
|
3646 #ifdef DEBUG |
|
3647 nsXPCOMCycleCollectionParticipant* participant; |
|
3648 CallQueryInterface(aHolder, &participant); |
|
3649 MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!"); |
|
3650 MOZ_ASSERT(participant->CheckForRightISupports(aHolder), |
|
3651 "The result of QIing a JS holder should be the same as ToSupports"); |
|
3652 #endif |
|
3653 DropJSObjectsImpl(static_cast<void*>(aHolder)); |
|
3654 } |
|
3655 |
|
3656 #ifdef DEBUG |
|
3657 bool |
|
3658 IsJSHolder(void* aHolder) |
|
3659 { |
|
3660 CollectorData *data = sCollectorData.get(); |
|
3661 |
|
3662 // We should have started the cycle collector by now, and not completely |
|
3663 // shut down. |
|
3664 MOZ_ASSERT(data); |
|
3665 // And we should have a runtime. |
|
3666 MOZ_ASSERT(data->mRuntime); |
|
3667 |
|
3668 return data->mRuntime->IsJSHolder(aHolder); |
|
3669 } |
|
3670 #endif |
|
3671 |
|
3672 void |
|
3673 DeferredFinalize(nsISupports* aSupports) |
|
3674 { |
|
3675 CollectorData *data = sCollectorData.get(); |
|
3676 |
|
3677 // We should have started the cycle collector by now, and not completely |
|
3678 // shut down. |
|
3679 MOZ_ASSERT(data); |
|
3680 // And we should have a runtime. |
|
3681 MOZ_ASSERT(data->mRuntime); |
|
3682 |
|
3683 data->mRuntime->DeferredFinalize(aSupports); |
|
3684 } |
|
3685 |
|
3686 void |
|
3687 DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, |
|
3688 DeferredFinalizeFunction aFunc, |
|
3689 void* aThing) |
|
3690 { |
|
3691 CollectorData *data = sCollectorData.get(); |
|
3692 |
|
3693 // We should have started the cycle collector by now, and not completely |
|
3694 // shut down. |
|
3695 MOZ_ASSERT(data); |
|
3696 // And we should have a runtime. |
|
3697 MOZ_ASSERT(data->mRuntime); |
|
3698 |
|
3699 data->mRuntime->DeferredFinalize(aAppendFunc, aFunc, aThing); |
|
3700 } |
|
3701 |
|
3702 } // namespace cyclecollector |
|
3703 } // namespace mozilla |
|
3704 |
|
3705 |
|
3706 MOZ_NEVER_INLINE static void |
|
3707 SuspectAfterShutdown(void* n, nsCycleCollectionParticipant* cp, |
|
3708 nsCycleCollectingAutoRefCnt* aRefCnt, |
|
3709 bool* aShouldDelete) |
|
3710 { |
|
3711 if (aRefCnt->get() == 0) { |
|
3712 if (!aShouldDelete) { |
|
3713 // The CC is shut down, so we can't be in the middle of an ICC. |
|
3714 CanonicalizeParticipant(&n, &cp); |
|
3715 aRefCnt->stabilizeForDeletion(); |
|
3716 cp->DeleteCycleCollectable(n); |
|
3717 } else { |
|
3718 *aShouldDelete = true; |
|
3719 } |
|
3720 } else { |
|
3721 // Make sure we'll get called again. |
|
3722 aRefCnt->RemoveFromPurpleBuffer(); |
|
3723 } |
|
3724 } |
|
3725 |
|
3726 void |
|
3727 NS_CycleCollectorSuspect3(void *n, nsCycleCollectionParticipant *cp, |
|
3728 nsCycleCollectingAutoRefCnt *aRefCnt, |
|
3729 bool* aShouldDelete) |
|
3730 { |
|
3731 CollectorData *data = sCollectorData.get(); |
|
3732 |
|
3733 // We should have started the cycle collector by now. |
|
3734 MOZ_ASSERT(data); |
|
3735 |
|
3736 if (MOZ_LIKELY(data->mCollector)) { |
|
3737 data->mCollector->Suspect(n, cp, aRefCnt); |
|
3738 return; |
|
3739 } |
|
3740 SuspectAfterShutdown(n, cp, aRefCnt, aShouldDelete); |
|
3741 } |
|
3742 |
|
3743 uint32_t |
|
3744 nsCycleCollector_suspectedCount() |
|
3745 { |
|
3746 CollectorData *data = sCollectorData.get(); |
|
3747 |
|
3748 // We should have started the cycle collector by now. |
|
3749 MOZ_ASSERT(data); |
|
3750 |
|
3751 if (!data->mCollector) { |
|
3752 return 0; |
|
3753 } |
|
3754 |
|
3755 return data->mCollector->SuspectedCount(); |
|
3756 } |
|
3757 |
|
3758 bool |
|
3759 nsCycleCollector_init() |
|
3760 { |
|
3761 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); |
|
3762 MOZ_ASSERT(!sCollectorData.initialized(), "Called twice!?"); |
|
3763 |
|
3764 return sCollectorData.init(); |
|
3765 } |
|
3766 |
|
3767 void |
|
3768 nsCycleCollector_startup() |
|
3769 { |
|
3770 MOZ_ASSERT(sCollectorData.initialized(), |
|
3771 "Forgot to call nsCycleCollector_init!"); |
|
3772 if (sCollectorData.get()) { |
|
3773 MOZ_CRASH(); |
|
3774 } |
|
3775 |
|
3776 CollectorData* data = new CollectorData; |
|
3777 data->mCollector = new nsCycleCollector(); |
|
3778 data->mRuntime = nullptr; |
|
3779 |
|
3780 sCollectorData.set(data); |
|
3781 } |
|
3782 |
|
3783 void |
|
3784 nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB) |
|
3785 { |
|
3786 CollectorData *data = sCollectorData.get(); |
|
3787 |
|
3788 // We should have started the cycle collector by now. |
|
3789 MOZ_ASSERT(data); |
|
3790 MOZ_ASSERT(data->mCollector); |
|
3791 |
|
3792 data->mCollector->SetBeforeUnlinkCallback(aCB); |
|
3793 } |
|
3794 |
|
3795 void |
|
3796 nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB) |
|
3797 { |
|
3798 CollectorData *data = sCollectorData.get(); |
|
3799 |
|
3800 // We should have started the cycle collector by now. |
|
3801 MOZ_ASSERT(data); |
|
3802 MOZ_ASSERT(data->mCollector); |
|
3803 |
|
3804 data->mCollector->SetForgetSkippableCallback(aCB); |
|
3805 } |
|
3806 |
|
3807 void |
|
3808 nsCycleCollector_forgetSkippable(bool aRemoveChildlessNodes, |
|
3809 bool aAsyncSnowWhiteFreeing) |
|
3810 { |
|
3811 CollectorData *data = sCollectorData.get(); |
|
3812 |
|
3813 // We should have started the cycle collector by now. |
|
3814 MOZ_ASSERT(data); |
|
3815 MOZ_ASSERT(data->mCollector); |
|
3816 |
|
3817 PROFILER_LABEL("CC", "nsCycleCollector_forgetSkippable"); |
|
3818 TimeLog timeLog; |
|
3819 data->mCollector->ForgetSkippable(aRemoveChildlessNodes, |
|
3820 aAsyncSnowWhiteFreeing); |
|
3821 timeLog.Checkpoint("ForgetSkippable()"); |
|
3822 } |
|
3823 |
|
3824 void |
|
3825 nsCycleCollector_dispatchDeferredDeletion(bool aContinuation) |
|
3826 { |
|
3827 CollectorData *data = sCollectorData.get(); |
|
3828 |
|
3829 if (!data || !data->mRuntime) { |
|
3830 return; |
|
3831 } |
|
3832 |
|
3833 data->mRuntime->DispatchDeferredDeletion(aContinuation); |
|
3834 } |
|
3835 |
|
3836 bool |
|
3837 nsCycleCollector_doDeferredDeletion() |
|
3838 { |
|
3839 CollectorData *data = sCollectorData.get(); |
|
3840 |
|
3841 // We should have started the cycle collector by now. |
|
3842 MOZ_ASSERT(data); |
|
3843 MOZ_ASSERT(data->mCollector); |
|
3844 MOZ_ASSERT(data->mRuntime); |
|
3845 |
|
3846 return data->mCollector->FreeSnowWhite(false); |
|
3847 } |
|
3848 |
|
3849 void |
|
3850 nsCycleCollector_collect(nsICycleCollectorListener *aManualListener) |
|
3851 { |
|
3852 CollectorData *data = sCollectorData.get(); |
|
3853 |
|
3854 // We should have started the cycle collector by now. |
|
3855 MOZ_ASSERT(data); |
|
3856 MOZ_ASSERT(data->mCollector); |
|
3857 |
|
3858 PROFILER_LABEL("CC", "nsCycleCollector_collect"); |
|
3859 SliceBudget unlimitedBudget; |
|
3860 data->mCollector->Collect(ManualCC, unlimitedBudget, aManualListener); |
|
3861 } |
|
3862 |
|
3863 void |
|
3864 nsCycleCollector_collectSlice(int64_t aSliceTime) |
|
3865 { |
|
3866 CollectorData *data = sCollectorData.get(); |
|
3867 |
|
3868 // We should have started the cycle collector by now. |
|
3869 MOZ_ASSERT(data); |
|
3870 MOZ_ASSERT(data->mCollector); |
|
3871 |
|
3872 PROFILER_LABEL("CC", "nsCycleCollector_collectSlice"); |
|
3873 SliceBudget budget; |
|
3874 if (aSliceTime > 0) { |
|
3875 budget = SliceBudget::TimeBudget(aSliceTime); |
|
3876 } else if (aSliceTime == 0) { |
|
3877 budget = SliceBudget::WorkBudget(1); |
|
3878 } |
|
3879 data->mCollector->Collect(SliceCC, budget, nullptr); |
|
3880 } |
|
3881 |
|
3882 void |
|
3883 nsCycleCollector_prepareForGarbageCollection() |
|
3884 { |
|
3885 CollectorData *data = sCollectorData.get(); |
|
3886 |
|
3887 MOZ_ASSERT(data); |
|
3888 |
|
3889 if (!data->mCollector) { |
|
3890 return; |
|
3891 } |
|
3892 |
|
3893 data->mCollector->PrepareForGarbageCollection(); |
|
3894 } |
|
3895 |
|
3896 void |
|
3897 nsCycleCollector_shutdown() |
|
3898 { |
|
3899 CollectorData *data = sCollectorData.get(); |
|
3900 |
|
3901 if (data) { |
|
3902 MOZ_ASSERT(data->mCollector); |
|
3903 PROFILER_LABEL("CC", "nsCycleCollector_shutdown"); |
|
3904 data->mCollector->Shutdown(); |
|
3905 data->mCollector = nullptr; |
|
3906 if (!data->mRuntime) { |
|
3907 delete data; |
|
3908 sCollectorData.set(nullptr); |
|
3909 } |
|
3910 } |
|
3911 } |