diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/base/CycleCollectedJSRuntime.h | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h new file mode 100644 index 0000000000..6f03d3ee99 --- /dev/null +++ b/xpcom/base/CycleCollectedJSRuntime.h @@ -0,0 +1,526 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_CycleCollectedJSRuntime_h +#define mozilla_CycleCollectedJSRuntime_h + +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/HashTable.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SegmentedVector.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/TypeDecls.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsStringFwd.h" +#include "nsTHashSet.h" + +class nsCycleCollectionNoteRootCallback; +class nsIException; +class nsWrapperCache; + +namespace mozilla { + +class JSGCThingParticipant : public nsCycleCollectionParticipant { + public: + constexpr JSGCThingParticipant() : nsCycleCollectionParticipant(false) {} + + NS_IMETHOD_(void) Root(void*) override { + MOZ_ASSERT(false, "Don't call Root on GC things"); + } + + NS_IMETHOD_(void) Unlink(void*) override { + MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) Unroot(void*) override { + MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) override { + MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing"); + } + + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant) +}; + +class JSZoneParticipant : public nsCycleCollectionParticipant { + public: + constexpr JSZoneParticipant() : nsCycleCollectionParticipant(false) {} + + NS_IMETHOD_(void) Root(void*) override { + MOZ_ASSERT(false, "Don't call Root on GC things"); + } + + NS_IMETHOD_(void) Unlink(void*) override { + MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) Unroot(void*) override { + MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void*) override { + MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing"); + } + + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant) +}; + +class IncrementalFinalizeRunnable; + +// A map from JS holders to tracer objects, where the values are stored in +// SegmentedVector to speed up iteration. +class JSHolderMap { + public: + enum WhichHolders { AllHolders, HoldersRequiredForGrayMarking }; + + class Iter; + + JSHolderMap(); + ~JSHolderMap() { MOZ_RELEASE_ASSERT(!mHasIterator); } + + bool Has(void* aHolder) const; + nsScriptObjectTracer* Get(void* aHolder) const; + nsScriptObjectTracer* Extract(void* aHolder); + void Put(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + private: + struct Entry { + void* mHolder; + nsScriptObjectTracer* mTracer; +#ifdef DEBUG + JS::Zone* mZone; +#endif + + Entry(); + Entry(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone); + }; + + using EntryMap = mozilla::HashMap<void*, Entry*, DefaultHasher<void*>, + InfallibleAllocPolicy>; + + using EntryVector = SegmentedVector<Entry, 256, InfallibleAllocPolicy>; + + using EntryVectorMap = + mozilla::HashMap<JS::Zone*, UniquePtr<EntryVector>, + DefaultHasher<JS::Zone*>, InfallibleAllocPolicy>; + + class EntryVectorIter; + + bool RemoveEntry(EntryVector& aJSHolders, Entry* aEntry); + + // A map from a holder pointer to a pointer to an entry in a vector. + EntryMap mJSHolderMap; + + // A vector of holders not associated with a particular zone or that can + // contain pointers to GC things in more than one zone. + EntryVector mAnyZoneJSHolders; + + // A map from a zone to a vector of holders that only contain pointers to GC + // things in that zone. + // + // Currently this will only contain wrapper cache wrappers since these are the + // only holders to pass a zone parameter through to AddJSHolder. + EntryVectorMap mPerZoneJSHolders; + + // Iterators can mutate the element vectors by removing stale elements. Allow + // at most one to exist at a time. + bool mHasIterator = false; +}; + +// An iterator over an EntryVector that skips over removed entries and removes +// them from the map. +class JSHolderMap::EntryVectorIter { + public: + EntryVectorIter(JSHolderMap& aMap, EntryVector& aVector) + : mHolderMap(aMap), mVector(aVector), mIter(aVector.Iter()) { + Settle(); + } + + const EntryVector& Vector() const { return mVector; } + + bool Done() const { return mIter.Done(); } + const Entry& Get() const { return mIter.Get(); } + void Next() { + mIter.Next(); + Settle(); + } + + operator const Entry*() const { return &Get(); } + const Entry* operator->() const { return &Get(); } + + private: + void Settle(); + friend class JSHolderMap::Iter; + + JSHolderMap& mHolderMap; + EntryVector& mVector; + EntryVector::IterImpl mIter; +}; + +class JSHolderMap::Iter { + public: + explicit Iter(JSHolderMap& aMap, WhichHolders aWhich = AllHolders); + + ~Iter() { + MOZ_RELEASE_ASSERT(mHolderMap.mHasIterator); + mHolderMap.mHasIterator = false; + } + + bool Done() const { return mIter.Done(); } + const Entry& Get() const { return mIter.Get(); } + void Next() { + mIter.Next(); + Settle(); + } + + // If the holders have been removed from the map while the iterator is live, + // then the iterator may point to a removed entry. Update the iterator to make + // sure it points to a valid entry or is done. + void UpdateForRemovals(); + + operator const Entry*() const { return &Get(); } + const Entry* operator->() const { return &Get(); } + + JS::Zone* Zone() const { return mZone; } + + private: + void Settle(); + + JSHolderMap& mHolderMap; + Vector<JS::Zone*, 1, InfallibleAllocPolicy> mZones; + JS::Zone* mZone = nullptr; + EntryVectorIter mIter; +}; + +class CycleCollectedJSRuntime { + friend class JSGCThingParticipant; + friend class JSZoneParticipant; + friend class IncrementalFinalizeRunnable; + friend class CycleCollectedJSContext; + + protected: + CycleCollectedJSRuntime(JSContext* aMainContext); + virtual ~CycleCollectedJSRuntime(); + + virtual void Shutdown(JSContext* cx); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + void UnmarkSkippableJSHolders(); + + virtual void TraverseAdditionalNativeRoots( + nsCycleCollectionNoteRootCallback& aCb) {} + virtual void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) {} + + virtual void CustomGCCallback(JSGCStatus aStatus) {} + virtual void CustomOutOfMemoryCallback() {} + + CycleCollectedJSContext* GetContext() { return mContext; } + + private: + void DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool DescribeCustomObjects(JSObject* aObject, const JSClass* aClasp, + char (&aName)[72]) const { + return false; // We did nothing. + } + + void NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + void NoteGCThingXPCOMChildren(const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool NoteCustomGCThingXPCOMChildren( + const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const { + return false; // We did nothing. + } + + enum TraverseSelect { TRAVERSE_CPP, TRAVERSE_FULL }; + + void TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb); + + void TraverseZone(JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb); + + static void TraverseObjectShim(void* aData, JS::GCCellPtr aThing, + const JS::AutoRequireNoGC& nogc); + + void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb); + + static void TraceBlackJS(JSTracer* aTracer, void* aData); + + // Trace gray JS roots until budget is exceeded and return whether we + // finished. + static bool TraceGrayJS(JSTracer* aTracer, js::SliceBudget& budget, + void* aData); + + static void GCCallback(JSContext* aContext, JSGCStatus aStatus, + JS::GCReason aReason, void* aData); + static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress, + const JS::GCDescription& aDesc); + static void GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::GCReason aReason, void* data); + static void OutOfMemoryCallback(JSContext* aContext, void* aData); + + static bool ContextCallback(JSContext* aCx, unsigned aOperation, void* aData); + + static void* BeforeWaitCallback(uint8_t* aMemory); + static void AfterWaitCallback(void* aCookie); + + virtual void TraceNativeBlackRoots(JSTracer* aTracer){}; + +#ifdef NS_BUILD_REFCNT_LOGGING + void TraceAllNativeGrayRoots(JSTracer* aTracer); +#endif + + bool TraceNativeGrayRoots(JSTracer* aTracer, JSHolderMap::WhichHolders aWhich, + js::SliceBudget& aBudget); + bool TraceJSHolders(JSTracer* aTracer, JSHolderMap::Iter& aIter, + js::SliceBudget& aBudget); + + public: + enum DeferredFinalizeType { + // Never finalize immediately, because it would be unsafe. + FinalizeLater, + // Finalize later if we can, but it is okay to do it immediately. + FinalizeIncrementally, + // Finalize immediately, for shutdown or testing purposes. + FinalizeNow, + }; + + void FinalizeDeferredThings(DeferredFinalizeType aType); + + virtual void PrepareForForgetSkippable() = 0; + virtual void BeginCycleCollectionCallback(mozilla::CCReason aReason) = 0; + virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0; + virtual void DispatchDeferredDeletion(bool aContinuation, + bool aPurge = false) = 0; + + // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in + // crash reports. Here are the values that can appear in the reports: + enum class OOMState : uint32_t { + // The condition has never happened. No entry appears in the crash report. + OK, + + // We are currently reporting the given condition. + // + // Suppose a crash report contains "JSLargeAllocationFailure: + // Reporting". This means we crashed while executing memory-pressure + // observers, trying to shake loose some memory. The large allocation in + // question did not return null: it is still on the stack. Had we not + // crashed, it would have been retried. + Reporting, + + // The condition has been reported since the last GC. + // + // If a crash report contains "JSOutOfMemory: Reported", that means a small + // allocation failed, and then we crashed, probably due to buggy + // error-handling code that ran after allocation returned null. + // + // This contrasts with "Reporting" which means that no error-handling code + // had executed yet. + Reported, + + // The condition has happened, but a GC cycle ended since then. + // + // GC is taken as a proxy for "we've been banging on the heap a good bit + // now and haven't crashed; the OOM was probably handled correctly". + Recovered + }; + + const char* OOMStateToString(const OOMState aOomState) const; + + // Returns true if OOM was reported and a new successful GC cycle hasn't + // occurred since. + bool OOMReported(); + + void SetLargeAllocationFailure(OOMState aNewState); + + void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState); + void OnGC(JSContext* aContext, JSGCStatus aStatus, JS::GCReason aReason); + void OnOutOfMemory(); + void OnLargeAllocationFailure(); + + JSRuntime* Runtime() { return mJSRuntime; } + const JSRuntime* Runtime() const { return mJSRuntime; } + + bool HasPendingIdleGCTask() const { + // Idle GC task associates with JSRuntime. + MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime()); + return mHasPendingIdleGCTask; + } + void SetPendingIdleGCTask() { + // Idle GC task associates with JSRuntime. + MOZ_ASSERT(Runtime()); + mHasPendingIdleGCTask = true; + } + void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; } + + void RunIdleTimeGCTask() { + if (HasPendingIdleGCTask()) { + JS::MaybeRunNurseryCollection(Runtime(), + JS::GCReason::EAGER_NURSERY_COLLECTION); + ClearPendingIdleGCTask(); + } + } + + bool IsIdleGCTaskNeeded() { + return !HasPendingIdleGCTask() && Runtime() && + JS::WantEagerMinorGC(Runtime()) != JS::GCReason::NO_REASON; + } + + public: + void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone); + void RemoveJSHolder(void* aHolder); +#ifdef DEBUG + void AssertNoObjectsToTrace(void* aPossibleJSHolder); +#endif + + nsCycleCollectionParticipant* GCThingParticipant(); + nsCycleCollectionParticipant* ZoneParticipant(); + + nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb); + virtual bool UsefulToMergeZones() const; + void FixWeakMappingGrayBits() const; + void CheckGrayBits() const; + bool AreGCGrayBitsValid() const; + void GarbageCollect(JS::GCOptions options, JS::GCReason aReason) const; + + // This needs to be an nsWrapperCache, not a JSObject, because we need to know + // when our object gets moved. But we can't trace it (and hence update our + // storage), because we do not want to keep it alive. nsWrapperCache handles + // this for us via its "object moved" handling. + void NurseryWrapperAdded(nsWrapperCache* aCache); + void JSObjectsTenured(); + + void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, void* aThing); + void DeferredFinalize(nsISupports* aSupports); + + void DumpJSHeap(FILE* aFile); + + // Add aZone to the set of zones waiting for a GC. + void AddZoneWaitingForGC(JS::Zone* aZone) { + mZonesWaitingForGC.Insert(aZone); + } + + static void OnZoneDestroyed(JS::GCContext* aGcx, JS::Zone* aZone); + + // Prepare any zones for GC that have been passed to AddZoneWaitingForGC() + // since the last GC or since the last call to PrepareWaitingZonesForGC(), + // whichever was most recent. If there were no such zones, prepare for a + // full GC. + void PrepareWaitingZonesForGC(); + + // Get the current thread's CycleCollectedJSRuntime. Returns null if there + // isn't one. + static CycleCollectedJSRuntime* Get(); + + void SetContext(CycleCollectedJSContext* aContext); + +#ifdef NIGHTLY_BUILD + bool GetRecentDevError(JSContext* aContext, + JS::MutableHandle<JS::Value> aError); + void ClearRecentDevError(); +#endif // defined(NIGHTLY_BUILD) + + private: + CycleCollectedJSContext* mContext; + + JSGCThingParticipant mGCThingCycleCollectorGlobal; + + JSZoneParticipant mJSZoneCycleCollectorGlobal; + + JSRuntime* mJSRuntime; + bool mHasPendingIdleGCTask; + + JS::GCSliceCallback mPrevGCSliceCallback; + + mozilla::TimeStamp mLatestNurseryCollectionStart; + + JSHolderMap mJSHolders; + Maybe<JSHolderMap::Iter> mHolderIter; + + using DeferredFinalizerTable = nsTHashMap<DeferredFinalizeFunction, void*>; + DeferredFinalizerTable mDeferredFinalizerTable; + + RefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable; + + OOMState mOutOfMemoryState; + OOMState mLargeAllocationFailureState; + + static const size_t kSegmentSize = 512; + SegmentedVector<nsWrapperCache*, kSegmentSize, InfallibleAllocPolicy> + mNurseryObjects; + + nsTHashSet<JS::Zone*> mZonesWaitingForGC; + + struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer { + void invoke(JS::Handle<JSObject*> global, Closure& closure) override; + }; + EnvironmentPreparer mEnvironmentPreparer; + +#ifdef DEBUG + bool mShutdownCalled; +#endif + +#ifdef NIGHTLY_BUILD + // Implementation of the error interceptor. + // Built on nightly only to avoid any possible performance impact on release + + struct ErrorInterceptor final : public JSErrorInterceptor { + virtual void interceptError(JSContext* cx, + JS::Handle<JS::Value> exn) override; + void Shutdown(JSRuntime* rt); + + // Copy of the details of the exception. + // We store this rather than the exception itself to avoid dealing with + // complicated garbage-collection scenarios, e.g. a JSContext being killed + // while we still hold onto an exception thrown from it. + struct ErrorDetails { + nsString mFilename; + nsString mMessage; + nsString mStack; + JSExnType mType; + uint32_t mLine; + uint32_t mColumn; + }; + + // If we have encountered at least one developer error, + // the first error we have encountered. Otherwise, or + // if we have reset since the latest error, `None`. + Maybe<ErrorDetails> mThrownError; + }; + ErrorInterceptor mErrorInterceptor; + +#endif // defined(NIGHTLY_BUILD) +}; + +void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSRuntime_h |