summaryrefslogtreecommitdiffstats
path: root/xpcom/base/CycleCollectedJSContext.h
diff options
context:
space:
mode:
Diffstat (limited to 'xpcom/base/CycleCollectedJSContext.h')
-rw-r--r--xpcom/base/CycleCollectedJSContext.h494
1 files changed, 494 insertions, 0 deletions
diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h
new file mode 100644
index 000000000..9415634b8
--- /dev/null
+++ b/xpcom/base/CycleCollectedJSContext.h
@@ -0,0 +1,494 @@
+/* -*- 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_CycleCollectedJSContext_h__
+#define mozilla_CycleCollectedJSContext_h__
+
+#include <queue>
+
+#include "mozilla/DeferredFinalize.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/SegmentedVector.h"
+#include "jsapi.h"
+#include "jsfriendapi.h"
+
+#include "nsCycleCollectionParticipant.h"
+#include "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+
+class nsCycleCollectionNoteRootCallback;
+class nsIException;
+class nsIRunnable;
+class nsThread;
+class nsWrapperCache;
+
+namespace js {
+struct Class;
+} // namespace js
+
+namespace mozilla {
+
+class JSGCThingParticipant: public nsCycleCollectionParticipant
+{
+public:
+ 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 Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb)
+ override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant)
+};
+
+class JSZoneParticipant : public nsCycleCollectionParticipant
+{
+public:
+ constexpr JSZoneParticipant(): nsCycleCollectionParticipant()
+ {
+ }
+
+ 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 Traverse(void* aPtr, nsCycleCollectionTraversalCallback& aCb)
+ override;
+
+ NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant)
+};
+
+class IncrementalFinalizeRunnable;
+
+// Contains various stats about the cycle collection.
+struct CycleCollectorResults
+{
+ CycleCollectorResults()
+ {
+ // Initialize here so when we increment mNumSlices the first time we're
+ // not using uninitialized memory.
+ Init();
+ }
+
+ void Init()
+ {
+ mForcedGC = false;
+ mMergedZones = false;
+ mAnyManual = false;
+ mVisitedRefCounted = 0;
+ mVisitedGCed = 0;
+ mFreedRefCounted = 0;
+ mFreedGCed = 0;
+ mFreedJSZones = 0;
+ mNumSlices = 1;
+ // mNumSlices is initialized to one, because we call Init() after the
+ // per-slice increment of mNumSlices has already occurred.
+ }
+
+ bool mForcedGC;
+ bool mMergedZones;
+ bool mAnyManual; // true if any slice of the CC was manually triggered, or at shutdown.
+ uint32_t mVisitedRefCounted;
+ uint32_t mVisitedGCed;
+ uint32_t mFreedRefCounted;
+ uint32_t mFreedGCed;
+ uint32_t mFreedJSZones;
+ uint32_t mNumSlices;
+};
+
+class CycleCollectedJSContext
+{
+ friend class JSGCThingParticipant;
+ friend class JSZoneParticipant;
+ friend class IncrementalFinalizeRunnable;
+protected:
+ CycleCollectedJSContext();
+ virtual ~CycleCollectedJSContext();
+
+ MOZ_IS_CLASS_INIT
+ nsresult Initialize(JSContext* aParentContext,
+ uint32_t aMaxBytes,
+ uint32_t aMaxNurseryBytes);
+
+ 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() {}
+ virtual void CustomLargeAllocationFailureCallback() {}
+
+ std::queue<nsCOMPtr<nsIRunnable>> mPromiseMicroTaskQueue;
+ std::queue<nsCOMPtr<nsIRunnable>> mDebuggerPromiseMicroTaskQueue;
+
+private:
+ void
+ DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb) const;
+
+ virtual bool
+ DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp,
+ char (&aName)[72]) const
+ {
+ return false; // We did nothing.
+ }
+
+ void
+ NoteGCThingJSChildren(JS::GCCellPtr aThing,
+ nsCycleCollectionTraversalCallback& aCb) const;
+
+ void
+ NoteGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj,
+ nsCycleCollectionTraversalCallback& aCb) const;
+
+ virtual bool
+ NoteCustomGCThingXPCOMChildren(const js::Class* 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);
+
+ void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb);
+
+ static void TraceBlackJS(JSTracer* aTracer, void* aData);
+ static void TraceGrayJS(JSTracer* aTracer, void* aData);
+ static void GCCallback(JSContext* aContext, JSGCStatus aStatus, void* aData);
+ static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress,
+ const JS::GCDescription& aDesc);
+ static void GCNurseryCollectionCallback(JSContext* aContext,
+ JS::GCNurseryProgress aProgress,
+ JS::gcreason::Reason aReason);
+ static void OutOfMemoryCallback(JSContext* aContext, void* aData);
+ static void LargeAllocationFailureCallback(void* aData);
+ static bool ContextCallback(JSContext* aCx, unsigned aOperation,
+ void* aData);
+ static JSObject* GetIncumbentGlobalCallback(JSContext* aCx);
+ static bool EnqueuePromiseJobCallback(JSContext* aCx,
+ JS::HandleObject aJob,
+ JS::HandleObject aAllocationSite,
+ JS::HandleObject aIncumbentGlobal,
+ void* aData);
+#ifdef SPIDERMONKEY_PROMISE
+ static void PromiseRejectionTrackerCallback(JSContext* aCx,
+ JS::HandleObject aPromise,
+ PromiseRejectionHandlingState state,
+ void* aData);
+#endif // SPIDERMONKEY_PROMISE
+
+ virtual void TraceNativeBlackRoots(JSTracer* aTracer) { };
+ void TraceNativeGrayRoots(JSTracer* aTracer);
+
+ void AfterProcessMicrotask(uint32_t aRecursionDepth);
+public:
+ void ProcessStableStateQueue();
+private:
+ void ProcessMetastableStateQueue(uint32_t aRecursionDepth);
+
+public:
+ enum DeferredFinalizeType {
+ FinalizeIncrementally,
+ FinalizeNow,
+ };
+
+ void FinalizeDeferredThings(DeferredFinalizeType aType);
+
+ // 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
+ };
+
+private:
+ void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState);
+ void OnGC(JSGCStatus aStatus);
+ void OnOutOfMemory();
+ void OnLargeAllocationFailure();
+
+public:
+ void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer);
+ void RemoveJSHolder(void* aHolder);
+#ifdef DEBUG
+ bool IsJSHolder(void* aHolder);
+ void AssertNoObjectsToTrace(void* aPossibleJSHolder);
+#endif
+
+ already_AddRefed<nsIException> GetPendingException() const;
+ void SetPendingException(nsIException* aException);
+
+ std::queue<nsCOMPtr<nsIRunnable>>& GetPromiseMicroTaskQueue();
+ std::queue<nsCOMPtr<nsIRunnable>>& GetDebuggerPromiseMicroTaskQueue();
+
+ nsCycleCollectionParticipant* GCThingParticipant();
+ nsCycleCollectionParticipant* ZoneParticipant();
+
+ nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb);
+ virtual bool UsefulToMergeZones() const;
+ void FixWeakMappingGrayBits() const;
+ bool AreGCGrayBitsValid() const;
+ void GarbageCollect(uint32_t aReason) const;
+
+ void NurseryWrapperAdded(nsWrapperCache* aCache);
+ void NurseryWrapperPreserved(JSObject* aWrapper);
+ void JSObjectsTenured();
+
+ void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc,
+ DeferredFinalizeFunction aFunc,
+ void* aThing);
+ void DeferredFinalize(nsISupports* aSupports);
+
+ void DumpJSHeap(FILE* aFile);
+
+ virtual void PrepareForForgetSkippable() = 0;
+ virtual void BeginCycleCollectionCallback() = 0;
+ virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0;
+ virtual void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) = 0;
+
+ JSContext* Context() const
+ {
+ MOZ_ASSERT(mJSContext);
+ return mJSContext;
+ }
+
+ JS::RootingContext* RootingCx() const
+ {
+ MOZ_ASSERT(mJSContext);
+ return JS::RootingContext::get(mJSContext);
+ }
+
+ bool MicroTaskCheckpointDisabled() const
+ {
+ return mDisableMicroTaskCheckpoint;
+ }
+
+ void DisableMicroTaskCheckpoint(bool aDisable)
+ {
+ mDisableMicroTaskCheckpoint = aDisable;
+ }
+
+ class MOZ_RAII AutoDisableMicroTaskCheckpoint
+ {
+ public:
+ AutoDisableMicroTaskCheckpoint()
+ : mCCJSCX(CycleCollectedJSContext::Get())
+ {
+ mOldValue = mCCJSCX->MicroTaskCheckpointDisabled();
+ mCCJSCX->DisableMicroTaskCheckpoint(true);
+ }
+
+ ~AutoDisableMicroTaskCheckpoint()
+ {
+ mCCJSCX->DisableMicroTaskCheckpoint(mOldValue);
+ }
+
+ CycleCollectedJSContext* mCCJSCX;
+ bool mOldValue;
+ };
+
+protected:
+ JSContext* MaybeContext() const { return mJSContext; }
+
+public:
+ // nsThread entrypoints
+ virtual void BeforeProcessTask(bool aMightBlock) { };
+ virtual void AfterProcessTask(uint32_t aRecursionDepth);
+
+ // microtask processor entry point
+ void AfterProcessMicrotask();
+
+ uint32_t RecursionDepth();
+
+ // Run in stable state (call through nsContentUtils)
+ void RunInStableState(already_AddRefed<nsIRunnable>&& aRunnable);
+ // This isn't in the spec at all yet, but this gets the behavior we want for IDB.
+ // Runs after the current microtask completes.
+ void RunInMetastableState(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ // Get the current thread's CycleCollectedJSContext. Returns null if there
+ // isn't one.
+ static CycleCollectedJSContext* Get();
+
+ // Add aZone to the set of zones waiting for a GC.
+ void AddZoneWaitingForGC(JS::Zone* aZone)
+ {
+ mZonesWaitingForGC.PutEntry(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();
+
+ // Queue an async microtask to the current main or worker thread.
+ virtual void DispatchToMicroTask(already_AddRefed<nsIRunnable> aRunnable);
+
+ // Storage for watching rejected promises waiting for some client to
+ // consume their rejection.
+#ifdef SPIDERMONKEY_PROMISE
+ // Promises in this list have been rejected in the last turn of the
+ // event loop without the rejection being handled.
+ // Note that this can contain nullptrs in place of promises removed because
+ // they're consumed before it'd be reported.
+ JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mUncaughtRejections;
+
+ // Promises in this list have previously been reported as rejected
+ // (because they were in the above list), but the rejection was handled
+ // in the last turn of the event loop.
+ JS::PersistentRooted<JS::GCVector<JSObject*, 0, js::SystemAllocPolicy>> mConsumedRejections;
+#else
+ // We store values as `nsISupports` to avoid adding compile-time dependencies
+ // from xpcom to dom/promise, but they can really only have a single concrete
+ // type.
+ nsTArray<nsCOMPtr<nsISupports /* Promise */>> mUncaughtRejections;
+ nsTArray<nsCOMPtr<nsISupports /* Promise */ >> mConsumedRejections;
+#endif // SPIDERMONKEY_PROMISE
+ nsTArray<nsCOMPtr<nsISupports /* UncaughtRejectionObserver */ >> mUncaughtRejectionObservers;
+
+private:
+ JSGCThingParticipant mGCThingCycleCollectorGlobal;
+
+ JSZoneParticipant mJSZoneCycleCollectorGlobal;
+
+ JSContext* mJSContext;
+
+ JS::GCSliceCallback mPrevGCSliceCallback;
+ JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback;
+
+ nsDataHashtable<nsPtrHashKey<void>, nsScriptObjectTracer*> mJSHolders;
+
+ typedef nsDataHashtable<nsFuncPtrHashKey<DeferredFinalizeFunction>, void*>
+ DeferredFinalizerTable;
+ DeferredFinalizerTable mDeferredFinalizerTable;
+
+ RefPtr<IncrementalFinalizeRunnable> mFinalizeRunnable;
+
+ nsCOMPtr<nsIException> mPendingException;
+ nsThread* mOwningThread; // Manual refcounting to avoid include hell.
+
+ struct RunInMetastableStateData
+ {
+ nsCOMPtr<nsIRunnable> mRunnable;
+ uint32_t mRecursionDepth;
+ };
+
+ nsTArray<nsCOMPtr<nsIRunnable>> mStableStateEvents;
+ nsTArray<RunInMetastableStateData> mMetastableStateEvents;
+ uint32_t mBaseRecursionDepth;
+ bool mDoingStableStates;
+
+ bool mDisableMicroTaskCheckpoint;
+
+ OOMState mOutOfMemoryState;
+ OOMState mLargeAllocationFailureState;
+
+ static const size_t kSegmentSize = 512;
+ SegmentedVector<nsWrapperCache*, kSegmentSize, InfallibleAllocPolicy>
+ mNurseryObjects;
+ SegmentedVector<JS::PersistentRooted<JSObject*>, kSegmentSize,
+ InfallibleAllocPolicy>
+ mPreservedNurseryObjects;
+
+ nsTHashtable<nsPtrHashKey<JS::Zone>> mZonesWaitingForGC;
+
+ struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer {
+ void invoke(JS::HandleObject scope, Closure& closure) override;
+ };
+ EnvironmentPreparer mEnvironmentPreparer;
+};
+
+void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer);
+
+// Returns true if the JS::TraceKind is one the cycle collector cares about.
+inline bool AddToCCKind(JS::TraceKind aKind)
+{
+ return aKind == JS::TraceKind::Object || aKind == JS::TraceKind::Script || aKind == JS::TraceKind::Scope;
+}
+
+bool
+GetBuildId(JS::BuildIdCharVector* aBuildID);
+
+} // namespace mozilla
+
+#endif // mozilla_CycleCollectedJSContext_h__