1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_CycleCollectedJSRuntime_h
8 #define mozilla_CycleCollectedJSRuntime_h
10 #include "mozilla/CycleCollectedJSContext.h"
11 #include "mozilla/DeferredFinalize.h"
12 #include "mozilla/HashTable.h"
13 #include "mozilla/Maybe.h"
14 #include "mozilla/MemoryReporting.h"
15 #include "mozilla/RefPtr.h"
16 #include "mozilla/SegmentedVector.h"
18 #include "jsfriendapi.h"
19 #include "js/TypeDecls.h"
21 #include "nsCycleCollectionParticipant.h"
22 #include "nsDataHashtable.h"
23 #include "nsHashKeys.h"
24 #include "nsStringFwd.h"
25 #include "nsTHashtable.h"
27 class nsCycleCollectionNoteRootCallback
;
33 class JSGCThingParticipant
: public nsCycleCollectionParticipant
{
35 constexpr JSGCThingParticipant() : nsCycleCollectionParticipant(false) {}
37 NS_IMETHOD_(void) Root(void*) override
{
38 MOZ_ASSERT(false, "Don't call Root on GC things");
41 NS_IMETHOD_(void) Unlink(void*) override
{
42 MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead");
45 NS_IMETHOD_(void) Unroot(void*) override
{
46 MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead");
49 NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr
) override
{
50 MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing");
53 NS_IMETHOD
TraverseNative(void* aPtr
,
54 nsCycleCollectionTraversalCallback
& aCb
) override
;
56 NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant
)
59 class JSZoneParticipant
: public nsCycleCollectionParticipant
{
61 constexpr JSZoneParticipant() : nsCycleCollectionParticipant(false) {}
63 NS_IMETHOD_(void) Root(void*) override
{
64 MOZ_ASSERT(false, "Don't call Root on GC things");
67 NS_IMETHOD_(void) Unlink(void*) override
{
68 MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead");
71 NS_IMETHOD_(void) Unroot(void*) override
{
72 MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead");
75 NS_IMETHOD_(void) DeleteCycleCollectable(void*) override
{
76 MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing");
79 NS_IMETHOD
TraverseNative(void* aPtr
,
80 nsCycleCollectionTraversalCallback
& aCb
) override
;
82 NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant
)
85 class IncrementalFinalizeRunnable
;
87 // A map from JS holders to tracer objects, where the values are stored in
88 // SegmentedVector to speed up iteration.
91 enum WhichHolders
{ AllHolders
, HoldersInCollectingZones
};
95 // Call functor |f| for each holder.
97 void ForEach(F
&& f
, WhichHolders aWhich
= AllHolders
);
99 bool Has(void* aHolder
) const;
100 nsScriptObjectTracer
* Get(void* aHolder
) const;
101 nsScriptObjectTracer
* GetAndRemove(void* aHolder
);
102 void Put(void* aHolder
, nsScriptObjectTracer
* aTracer
, JS::Zone
* aZone
);
104 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const;
109 nsScriptObjectTracer
* mTracer
;
115 Entry(void* aHolder
, nsScriptObjectTracer
* aTracer
, JS::Zone
* aZone
);
118 using EntryMap
= mozilla::HashMap
<void*, Entry
*, DefaultHasher
<void*>,
119 InfallibleAllocPolicy
>;
121 using EntryVector
= SegmentedVector
<Entry
, 256, InfallibleAllocPolicy
>;
123 using EntryVectorMap
=
124 mozilla::HashMap
<JS::Zone
*, UniquePtr
<EntryVector
>,
125 DefaultHasher
<JS::Zone
*>, InfallibleAllocPolicy
>;
127 template <typename F
>
128 void ForEach(EntryVector
& aJSHolders
, const F
& f
, JS::Zone
* aZone
);
130 bool RemoveEntry(EntryVector
& aJSHolders
, Entry
* aEntry
);
132 // A map from a holder pointer to a pointer to an entry in a vector.
133 EntryMap mJSHolderMap
;
135 // A vector of holders not associated with a particular zone or that can
136 // contain pointers to GC things in more than one zone.
137 EntryVector mAnyZoneJSHolders
;
139 // A map from a zone to a vector of holders that only contain pointers to GC
140 // things in that zone.
142 // Currently this will only contain wrapper cache wrappers since these are the
143 // only holders to pass a zone parameter through to AddJSHolder.
144 EntryVectorMap mPerZoneJSHolders
;
147 class CycleCollectedJSRuntime
{
148 friend class JSGCThingParticipant
;
149 friend class JSZoneParticipant
;
150 friend class IncrementalFinalizeRunnable
;
151 friend class CycleCollectedJSContext
;
154 CycleCollectedJSRuntime(JSContext
* aMainContext
);
155 virtual ~CycleCollectedJSRuntime();
157 virtual void Shutdown(JSContext
* cx
);
159 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const;
160 void UnmarkSkippableJSHolders();
162 virtual void TraverseAdditionalNativeRoots(
163 nsCycleCollectionNoteRootCallback
& aCb
) {}
164 virtual void TraceAdditionalNativeGrayRoots(JSTracer
* aTracer
) {}
166 virtual void CustomGCCallback(JSGCStatus aStatus
) {}
167 virtual void CustomOutOfMemoryCallback() {}
169 CycleCollectedJSContext
* GetContext() { return mContext
; }
172 void DescribeGCThing(bool aIsMarked
, JS::GCCellPtr aThing
,
173 nsCycleCollectionTraversalCallback
& aCb
) const;
175 virtual bool DescribeCustomObjects(JSObject
* aObject
, const JSClass
* aClasp
,
176 char (&aName
)[72]) const {
177 return false; // We did nothing.
180 void NoteGCThingJSChildren(JS::GCCellPtr aThing
,
181 nsCycleCollectionTraversalCallback
& aCb
) const;
183 void NoteGCThingXPCOMChildren(const JSClass
* aClasp
, JSObject
* aObj
,
184 nsCycleCollectionTraversalCallback
& aCb
) const;
186 virtual bool NoteCustomGCThingXPCOMChildren(
187 const JSClass
* aClasp
, JSObject
* aObj
,
188 nsCycleCollectionTraversalCallback
& aCb
) const {
189 return false; // We did nothing.
192 enum TraverseSelect
{ TRAVERSE_CPP
, TRAVERSE_FULL
};
194 void TraverseGCThing(TraverseSelect aTs
, JS::GCCellPtr aThing
,
195 nsCycleCollectionTraversalCallback
& aCb
);
197 void TraverseZone(JS::Zone
* aZone
, nsCycleCollectionTraversalCallback
& aCb
);
199 static void TraverseObjectShim(void* aData
, JS::GCCellPtr aThing
,
200 const JS::AutoRequireNoGC
& nogc
);
202 void TraverseNativeRoots(nsCycleCollectionNoteRootCallback
& aCb
);
204 static void TraceBlackJS(JSTracer
* aTracer
, void* aData
);
205 static void TraceGrayJS(JSTracer
* aTracer
, void* aData
);
206 static void GCCallback(JSContext
* aContext
, JSGCStatus aStatus
,
207 JS::GCReason aReason
, void* aData
);
208 static void GCSliceCallback(JSContext
* aContext
, JS::GCProgress aProgress
,
209 const JS::GCDescription
& aDesc
);
210 static void GCNurseryCollectionCallback(JSContext
* aContext
,
211 JS::GCNurseryProgress aProgress
,
212 JS::GCReason aReason
);
213 static void OutOfMemoryCallback(JSContext
* aContext
, void* aData
);
215 static bool ContextCallback(JSContext
* aCx
, unsigned aOperation
, void* aData
);
217 static void* BeforeWaitCallback(uint8_t* aMemory
);
218 static void AfterWaitCallback(void* aCookie
);
220 virtual void TraceNativeBlackRoots(JSTracer
* aTracer
){};
221 void TraceNativeGrayRoots(JSTracer
* aTracer
,
222 JSHolderMap::WhichHolders aWhich
);
225 void FinalizeDeferredThings(
226 CycleCollectedJSContext::DeferredFinalizeType aType
);
228 virtual void PrepareForForgetSkippable() = 0;
229 virtual void BeginCycleCollectionCallback() = 0;
230 virtual void EndCycleCollectionCallback(CycleCollectorResults
& aResults
) = 0;
231 virtual void DispatchDeferredDeletion(bool aContinuation
,
232 bool aPurge
= false) = 0;
234 // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in
235 // crash reports. Here are the values that can appear in the reports:
236 enum class OOMState
: uint32_t {
237 // The condition has never happened. No entry appears in the crash report.
240 // We are currently reporting the given condition.
242 // Suppose a crash report contains "JSLargeAllocationFailure:
243 // Reporting". This means we crashed while executing memory-pressure
244 // observers, trying to shake loose some memory. The large allocation in
245 // question did not return null: it is still on the stack. Had we not
246 // crashed, it would have been retried.
249 // The condition has been reported since the last GC.
251 // If a crash report contains "JSOutOfMemory: Reported", that means a small
252 // allocation failed, and then we crashed, probably due to buggy
253 // error-handling code that ran after allocation returned null.
255 // This contrasts with "Reporting" which means that no error-handling code
259 // The condition has happened, but a GC cycle ended since then.
261 // GC is taken as a proxy for "we've been banging on the heap a good bit
262 // now and haven't crashed; the OOM was probably handled correctly".
266 const char* OOMStateToString(const OOMState aOomState
) const;
268 void SetLargeAllocationFailure(OOMState aNewState
);
270 void AnnotateAndSetOutOfMemory(OOMState
* aStatePtr
, OOMState aNewState
);
271 void OnGC(JSContext
* aContext
, JSGCStatus aStatus
, JS::GCReason aReason
);
272 void OnOutOfMemory();
273 void OnLargeAllocationFailure();
275 JSRuntime
* Runtime() { return mJSRuntime
; }
276 const JSRuntime
* Runtime() const { return mJSRuntime
; }
278 bool HasPendingIdleGCTask() const {
279 // Idle GC task associates with JSRuntime.
280 MOZ_ASSERT_IF(mHasPendingIdleGCTask
, Runtime());
281 return mHasPendingIdleGCTask
;
283 void SetPendingIdleGCTask() {
284 // Idle GC task associates with JSRuntime.
285 MOZ_ASSERT(Runtime());
286 mHasPendingIdleGCTask
= true;
288 void ClearPendingIdleGCTask() { mHasPendingIdleGCTask
= false; }
290 void RunIdleTimeGCTask() {
291 if (HasPendingIdleGCTask()) {
292 JS::RunIdleTimeGCTask(Runtime());
293 ClearPendingIdleGCTask();
297 bool IsIdleGCTaskNeeded() {
298 return !HasPendingIdleGCTask() && Runtime() &&
299 JS::IsIdleGCTaskNeeded(Runtime());
303 void AddJSHolder(void* aHolder
, nsScriptObjectTracer
* aTracer
,
305 void RemoveJSHolder(void* aHolder
);
307 void AssertNoObjectsToTrace(void* aPossibleJSHolder
);
310 nsCycleCollectionParticipant
* GCThingParticipant();
311 nsCycleCollectionParticipant
* ZoneParticipant();
313 nsresult
TraverseRoots(nsCycleCollectionNoteRootCallback
& aCb
);
314 virtual bool UsefulToMergeZones() const;
315 void FixWeakMappingGrayBits() const;
316 void CheckGrayBits() const;
317 bool AreGCGrayBitsValid() const;
318 void GarbageCollect(JS::GCReason aReason
) const;
320 // This needs to be an nsWrapperCache, not a JSObject, because we need to know
321 // when our object gets moved. But we can't trace it (and hence update our
322 // storage), because we do not want to keep it alive. nsWrapperCache handles
323 // this for us via its "object moved" handling.
324 void NurseryWrapperAdded(nsWrapperCache
* aCache
);
325 void NurseryWrapperPreserved(JSObject
* aWrapper
);
326 void JSObjectsTenured();
328 void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc
,
329 DeferredFinalizeFunction aFunc
, void* aThing
);
330 void DeferredFinalize(nsISupports
* aSupports
);
332 void DumpJSHeap(FILE* aFile
);
334 // Add aZone to the set of zones waiting for a GC.
335 void AddZoneWaitingForGC(JS::Zone
* aZone
) {
336 mZonesWaitingForGC
.PutEntry(aZone
);
339 static void OnZoneDestroyed(JSFreeOp
* aFop
, JS::Zone
* aZone
);
341 // Prepare any zones for GC that have been passed to AddZoneWaitingForGC()
342 // since the last GC or since the last call to PrepareWaitingZonesForGC(),
343 // whichever was most recent. If there were no such zones, prepare for a
345 void PrepareWaitingZonesForGC();
347 // Get the current thread's CycleCollectedJSRuntime. Returns null if there
349 static CycleCollectedJSRuntime
* Get();
351 void SetContext(CycleCollectedJSContext
* aContext
);
354 bool GetRecentDevError(JSContext
* aContext
,
355 JS::MutableHandle
<JS::Value
> aError
);
356 void ClearRecentDevError();
357 #endif // defined(NIGHTLY_BUILD)
360 CycleCollectedJSContext
* mContext
;
362 JSGCThingParticipant mGCThingCycleCollectorGlobal
;
364 JSZoneParticipant mJSZoneCycleCollectorGlobal
;
366 JSRuntime
* mJSRuntime
;
367 bool mHasPendingIdleGCTask
;
369 JS::GCSliceCallback mPrevGCSliceCallback
;
370 JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback
;
372 mozilla::TimeStamp mLatestNurseryCollectionStart
;
374 JSHolderMap mJSHolders
;
376 typedef nsDataHashtable
<nsFuncPtrHashKey
<DeferredFinalizeFunction
>, void*>
377 DeferredFinalizerTable
;
378 DeferredFinalizerTable mDeferredFinalizerTable
;
380 RefPtr
<IncrementalFinalizeRunnable
> mFinalizeRunnable
;
382 OOMState mOutOfMemoryState
;
383 OOMState mLargeAllocationFailureState
;
385 static const size_t kSegmentSize
= 512;
386 SegmentedVector
<nsWrapperCache
*, kSegmentSize
, InfallibleAllocPolicy
>
388 SegmentedVector
<JS::PersistentRooted
<JSObject
*>, kSegmentSize
,
389 InfallibleAllocPolicy
>
390 mPreservedNurseryObjects
;
392 nsTHashtable
<nsPtrHashKey
<JS::Zone
>> mZonesWaitingForGC
;
394 struct EnvironmentPreparer
: public js::ScriptEnvironmentPreparer
{
395 void invoke(JS::HandleObject global
, Closure
& closure
) override
;
397 EnvironmentPreparer mEnvironmentPreparer
;
400 bool mShutdownCalled
;
404 // Implementation of the error interceptor.
405 // Built on nightly only to avoid any possible performance impact on release
407 struct ErrorInterceptor final
: public JSErrorInterceptor
{
408 virtual void interceptError(JSContext
* cx
, JS::HandleValue exn
) override
;
409 void Shutdown(JSRuntime
* rt
);
411 // Copy of the details of the exception.
412 // We store this rather than the exception itself to avoid dealing with
413 // complicated garbage-collection scenarios, e.g. a JSContext being killed
414 // while we still hold onto an exception thrown from it.
415 struct ErrorDetails
{
424 // If we have encountered at least one developer error,
425 // the first error we have encountered. Otherwise, or
426 // if we have reset since the latest error, `None`.
427 Maybe
<ErrorDetails
> mThrownError
;
429 ErrorInterceptor mErrorInterceptor
;
431 #endif // defined(NIGHTLY_BUILD)
434 void TraceScriptHolder(nsISupports
* aHolder
, JSTracer
* aTracer
);
436 } // namespace mozilla
438 #endif // mozilla_CycleCollectedJSRuntime_h