From a10e38916cc6fec748c5fa59d0bb589cbd2a3ebc Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Sat, 14 Jan 2012 18:58:05 +0200 Subject: [PATCH] Bug 716518 - Add skip* phases to cycle collector, r=mccr8 --HG-- extra : rebase_source : c53a42bbbc430384df37d0aeb0d882c09665f672 --- xpcom/base/nsCycleCollector.cpp | 155 ++++++++++++++++++---- xpcom/base/nsCycleCollector.h | 13 ++ xpcom/glue/nsCycleCollectionParticipant.h | 103 ++++++++++++++ xpcom/glue/nsISupportsImpl.h | 12 ++ 4 files changed, 258 insertions(+), 25 deletions(-) diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp index 4ebe2657eba3..9978b8dc2912 100644 --- a/xpcom/base/nsCycleCollector.cpp +++ b/xpcom/base/nsCycleCollector.cpp @@ -867,6 +867,11 @@ public: void SelectPointers(GCGraphBuilder &builder); + // RemoveSkippable removes entries from the purple buffer if + // nsPurpleBufferEntry::mObject is null or if the object's + // nsXPCOMCycleCollectionParticipant::CanSkip() returns true. + void RemoveSkippable(); + #ifdef DEBUG_CC void NoteAll(GCGraphBuilder &builder); @@ -985,8 +990,22 @@ void nsPurpleBuffer::SelectPointers(GCGraphBuilder &aBuilder) { #ifdef DEBUG_CC + // Can't use mCount here, since it may include null entries. + PRUint32 realCount = 0; + for (Block *b = &mFirstBlock; b; b = b->mNext) { + for (nsPurpleBufferEntry *e = b->mEntries, + *eEnd = ArrayEnd(b->mEntries); + e != eEnd; ++e) { + if (!(PRUword(e->mObject) & PRUword(1))) { + if (e->mObject) { + ++realCount; + } + } + } + } + NS_ABORT_IF_FALSE(mCompatObjects.Count() + mNormalObjects.Count() == - mCount, + realCount, "count out of sync"); #endif @@ -1006,16 +1025,7 @@ nsPurpleBuffer::SelectPointers(GCGraphBuilder &aBuilder) // This is a real entry (rather than something on the // free list). if (!e->mObject || AddPurpleRoot(aBuilder, e->mObject)) { -#ifdef DEBUG_CC - mNormalObjects.RemoveEntry(e->mObject); -#endif - --mCount; - // Put this entry on the free list in case some - // call to AddPurpleRoot fails and we don't rebuild - // the free list below. - e->mNextInFreeList = (nsPurpleBufferEntry*) - (PRUword(mFreeList) | PRUword(1)); - mFreeList = e; + Remove(e); } } } @@ -1085,6 +1095,9 @@ struct nsCycleCollector nsPurpleBuffer mPurpleBuf; + CC_BeforeUnlinkCallback mBeforeUnlinkCB; + CC_ForgetSkippableCallback mForgetSkippableCB; + void RegisterRuntime(PRUint32 langID, nsCycleCollectionLanguageRuntime *rt); nsCycleCollectionLanguageRuntime * GetRuntime(PRUint32 langID); @@ -1095,6 +1108,8 @@ struct nsCycleCollector void ScanRoots(); void ScanWeakMaps(); + void ForgetSkippable(); + // returns whether anything was collected bool CollectWhite(nsICycleCollectorListener *aListener); @@ -1139,6 +1154,8 @@ struct nsCycleCollector void Allocated(void *n, size_t sz); void Freed(void *n); + void LogPurpleRemoval(void* aObject); + void ExplainLiveExpectedGarbage(); bool CreateReversedEdges(); void DestroyReversedEdges(); @@ -1772,7 +1789,8 @@ GCGraphBuilder::NoteXPCOMChild(nsISupports *child) nsXPCOMCycleCollectionParticipant *cp; ToParticipant(child, &cp); - if (cp) { + if (cp && !cp->CanSkipThis(child)) { + PtrInfo *childPi = AddNode(child, cp, nsIProgrammingLanguage::CPLUSPLUS); if (!childPi) return; @@ -1878,7 +1896,7 @@ GCGraphBuilder::AddWeakMapNode(void *node) cp = mRuntimes[nsIProgrammingLanguage::JAVASCRIPT]->ToParticipant(node); NS_ASSERTION(cp, "Javascript runtime participant should be non-null."); - return AddNode(node, cp); + return AddNode(node, cp, nsIProgrammingLanguage::JAVASCRIPT); } NS_IMETHODIMP_(void) @@ -1905,10 +1923,12 @@ AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root) nsXPCOMCycleCollectionParticipant *cp; ToParticipant(root, &cp); - PtrInfo *pinfo = builder.AddNode(root, cp, - nsIProgrammingLanguage::CPLUSPLUS); - if (!pinfo) { - return false; + if (!cp->CanSkipInCC(root)) { + PtrInfo *pinfo = builder.AddNode(root, cp, + nsIProgrammingLanguage::CPLUSPLUS); + if (!pinfo) { + return false; + } } cp->UnmarkPurple(root); @@ -1916,6 +1936,32 @@ AddPurpleRoot(GCGraphBuilder &builder, nsISupports *root) return true; } +void +nsPurpleBuffer::RemoveSkippable() +{ + // Walk through all the blocks. + for (Block *b = &mFirstBlock; b; b = b->mNext) { + for (nsPurpleBufferEntry *e = b->mEntries, + *eEnd = ArrayEnd(b->mEntries); + e != eEnd; ++e) { + if (!(PRUword(e->mObject) & PRUword(1))) { + // This is a real entry (rather than something on the + // free list). + if (e->mObject) { + nsISupports* o = canonicalize(e->mObject); + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(o, &cp); + if (!cp->CanSkip(o)) { + continue; + } + cp->UnmarkPurple(o); + } + Remove(e); + } + } + } +} + #ifdef DEBUG_CC static PLDHashOperator noteAllCallback(nsVoidPtrHashKey* key, void* userArg) @@ -1949,6 +1995,19 @@ nsCycleCollector::SelectPurple(GCGraphBuilder &builder) mPurpleBuf.SelectPointers(builder); } +void +nsCycleCollector::ForgetSkippable() +{ + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nsnull, "cycle-collector-forget-skippable", nsnull); + } + mPurpleBuf.RemoveSkippable(); + if (mForgetSkippableCB) { + mForgetSkippableCB(); + } +} + void nsCycleCollector::MarkRoots(GCGraphBuilder &builder) { @@ -2130,6 +2189,9 @@ nsCycleCollector::CollectWhite(nsICycleCollectorListener *aListener) } } + if (mBeforeUnlinkCB) { + mBeforeUnlinkCB(); + } #if defined(DEBUG_CC) && !defined(__MINGW32__) && defined(WIN32) struct _CrtMemState ms1, ms2; _CrtMemCheckpoint(&ms1); @@ -2387,10 +2449,12 @@ nsCycleCollector::nsCycleCollector() : mVisitedGCed(0), #ifdef DEBUG_CC mPurpleBuf(mParams, mStats), - mPtrLog(nsnull) + mPtrLog(nsnull), #else - mPurpleBuf(mParams) + mPurpleBuf(mParams), #endif + mBeforeUnlinkCB(nsnull), + mForgetSkippableCB(nsnull) { #ifdef DEBUG_CC mExpectedGarbage.Init(); @@ -2657,6 +2721,27 @@ nsCycleCollector::Forget2(nsPurpleBufferEntry *e) return false; #ifdef DEBUG_CC + LogPurpleRemoval(e->mObject); +#endif + + mPurpleBuf.Remove(e); + return true; +} + +#ifdef DEBUG_CC +void +nsCycleCollector_logPurpleRemoval(void* aObject) +{ + if (sCollector) { + sCollector->LogPurpleRemoval(aObject); + } +} + +void +nsCycleCollector::LogPurpleRemoval(void* aObject) +{ + AbortIfOffMainThreadIfCheckFast(); + mStats.mForgetNode++; #ifndef __MINGW32__ @@ -2667,15 +2752,11 @@ nsCycleCollector::Forget2(nsPurpleBufferEntry *e) if (mParams.mLogPointers) { if (!mPtrLog) mPtrLog = fopen("pointer_log", "w"); - fprintf(mPtrLog, "F %p\n", static_cast(e->mObject)); + fprintf(mPtrLog, "F %p\n", aObject); } -#endif - - mPurpleBuf.Remove(e); - return true; + mPurpleBuf.mNormalObjects.RemoveEntry(aObject); } -#ifdef DEBUG_CC void nsCycleCollector::Allocated(void *n, size_t sz) { @@ -3727,6 +3808,30 @@ nsCycleCollector_startup() return rv; } +void +nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB) +{ + if (sCollector) { + sCollector->mBeforeUnlinkCB = aCB; + } +} + +void +nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB) +{ + if (sCollector) { + sCollector->mForgetSkippableCB = aCB; + } +} + +void +nsCycleCollector_forgetSkippable() +{ + if (sCollector) { + sCollector->ForgetSkippable(); + } +} + PRUint32 nsCycleCollector_collect(nsICycleCollectorListener *aListener) { diff --git a/xpcom/base/nsCycleCollector.h b/xpcom/base/nsCycleCollector.h index 5b4fc121ed08..40913fded21f 100644 --- a/xpcom/base/nsCycleCollector.h +++ b/xpcom/base/nsCycleCollector.h @@ -63,6 +63,19 @@ struct nsCycleCollectionLanguageRuntime }; nsresult nsCycleCollector_startup(); + +typedef void (*CC_BeforeUnlinkCallback)(void); +void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB); + +typedef void (*CC_ForgetSkippableCallback)(void); +void nsCycleCollector_setForgetSkippableCallback(CC_ForgetSkippableCallback aCB); + +void nsCycleCollector_forgetSkippable(); + +#ifdef DEBUG_CC +void nsCycleCollector_logPurpleRemoval(void* aObject); +#endif + // Returns the number of collected nodes. PRUint32 nsCycleCollector_collect(nsICycleCollectorListener *aListener); PRUint32 nsCycleCollector_suspectedCount(); diff --git a/xpcom/glue/nsCycleCollectionParticipant.h b/xpcom/glue/nsCycleCollectionParticipant.h index 7ae8be539733..f2f88c353e58 100644 --- a/xpcom/glue/nsCycleCollectionParticipant.h +++ b/xpcom/glue/nsCycleCollectionParticipant.h @@ -164,6 +164,9 @@ class NS_COM_GLUE nsXPCOMCycleCollectionParticipant : public nsScriptObjectTracer { public: + nsXPCOMCycleCollectionParticipant() : mMightSkip(false) {} + nsXPCOMCycleCollectionParticipant(bool aSkip) : mMightSkip(aSkip) {} + NS_IMETHOD Traverse(void *p, nsCycleCollectionTraversalCallback &cb); NS_IMETHOD Root(void *p); @@ -175,6 +178,49 @@ public: NS_IMETHOD_(void) UnmarkPurple(nsISupports *p); bool CheckForRightISupports(nsISupports *s); + + // If CanSkip returns true, p is removed from the purple buffer during + // a call to nsCycleCollector_forgetSkippable(). + // Note, calling CanSkip may remove objects from the purple buffer! + bool CanSkip(void *p) + { + return mMightSkip ? CanSkipReal(p) : false; + } + + // If CanSkipInCC returns true, p is skipped when selecting roots for the + // cycle collector graph. + // Note, calling CanSkipInCC may remove other objects from the purple buffer! + bool CanSkipInCC(void *p) + { + return mMightSkip ? CanSkipInCCReal(p) : false; + } + + // If CanSkipThis returns true, p is not added to the graph. + // This method is called during cycle collection, so don't + // change the state of any objects! + bool CanSkipThis(void *p) + { + return mMightSkip ? CanSkipThisReal(p) : false; + } +protected: + NS_IMETHOD_(bool) CanSkipReal(void *p) + { + NS_ASSERTION(false, "Forgot to implement CanSkipReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipInCCReal(void *p) + { + NS_ASSERTION(false, "Forgot to implement CanSkipInCCReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipThisReal(void *p) + { + NS_ASSERTION(false, "Forgot to implement CanSkipThisReal?"); + return false; + } + +private: + bool mMightSkip; }; #undef IMETHOD_VISIBILITY @@ -245,6 +291,45 @@ public: #define NS_CYCLE_COLLECTION_UPCAST(obj, clazz) \ NS_CYCLE_COLLECTION_CLASSNAME(clazz)::Upcast(obj) +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipReal(void *p) \ + { \ + nsISupports *s = static_cast(p); \ + NS_ASSERTION(CheckForRightISupports(s), \ + "not the nsISupports pointer we expect"); \ + _class *tmp = Downcast(s); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END \ + return false; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipInCCReal(void *p) \ + { \ + nsISupports *s = static_cast(p); \ + NS_ASSERTION(CheckForRightISupports(s), \ + "not the nsISupports pointer we expect"); \ + _class *tmp = Downcast(s); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END \ + return false; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipThisReal(void *p) \ + { \ + nsISupports *s = static_cast(p); \ + NS_ASSERTION(CheckForRightISupports(s), \ + "not the nsISupports pointer we expect"); \ + _class *tmp = Downcast(s); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END \ + return false; \ + } + /////////////////////////////////////////////////////////////////////////////// // Helpers for implementing nsCycleCollectionParticipant::Unlink /////////////////////////////////////////////////////////////////////////////// @@ -552,6 +637,24 @@ class NS_CYCLE_COLLECTION_INNERCLASS \ }; \ NS_CYCLE_COLLECTION_PARTICIPANT_INSTANCE +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _base) \ +class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant \ +{ \ +public: \ + NS_CYCLE_COLLECTION_INNERCLASS () : nsXPCOMCycleCollectionParticipant(true) {} \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) Trace(void *p, TraceCallback cb, void *closure); \ +protected: \ + NS_IMETHOD_(bool) CanSkipReal(void *p); \ + NS_IMETHOD_(bool) CanSkipInCCReal(void *p); \ + NS_IMETHOD_(bool) CanSkipThisReal(void *p); \ +}; \ +NS_CYCLE_COLLECTION_PARTICIPANT_INSTANCE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class) + #define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(_class) \ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class) diff --git a/xpcom/glue/nsISupportsImpl.h b/xpcom/glue/nsISupportsImpl.h index 4d5c9f6a9efb..9645053ea660 100644 --- a/xpcom/glue/nsISupportsImpl.h +++ b/xpcom/glue/nsISupportsImpl.h @@ -226,6 +226,18 @@ public: mTagged = NS_CCAR_REFCNT_TO_TAGGED(refcount); } + void RemovePurple() + { + NS_ASSERTION(IsPurple(), "must be purple"); +#ifdef DEBUG_CC + nsCycleCollector_logPurpleRemoval( + NS_CCAR_TAGGED_TO_PURPLE_ENTRY(mTagged)->mObject); +#endif + // The entry will be added to the free list later. + NS_CCAR_TAGGED_TO_PURPLE_ENTRY(mTagged)->mObject = nsnull; + unmarkPurple(); + } + bool IsPurple() const { NS_ASSERTION(mTagged != NS_CCAR_TAGGED_STABILIZED_REFCNT,