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 "nsTHashMap.h"
23 #include "nsHashKeys.h"
24 #include "nsStringFwd.h"
25 #include "nsTHashSet.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
, HoldersRequiredForGrayMarking
};
96 ~JSHolderMap() { MOZ_RELEASE_ASSERT(!mHasIterator
); }
98 bool Has(void* aHolder
) const;
99 nsScriptObjectTracer
* Get(void* aHolder
) const;
100 nsScriptObjectTracer
* Extract(void* aHolder
);
101 void Put(void* aHolder
, nsScriptObjectTracer
* aTracer
, JS::Zone
* aZone
);
103 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf
) const;
108 nsScriptObjectTracer
* mTracer
;
114 Entry(void* aHolder
, nsScriptObjectTracer
* aTracer
, JS::Zone
* aZone
);
117 using EntryMap
= mozilla::HashMap
<void*, Entry
*, DefaultHasher
<void*>,
118 InfallibleAllocPolicy
>;
120 using EntryVector
= SegmentedVector
<Entry
, 256, InfallibleAllocPolicy
>;
122 using EntryVectorMap
=
123 mozilla::HashMap
<JS::Zone
*, UniquePtr
<EntryVector
>,
124 DefaultHasher
<JS::Zone
*>, InfallibleAllocPolicy
>;
126 class EntryVectorIter
;
128 bool RemoveEntry(EntryVector
& aJSHolders
, Entry
* aEntry
);
130 // A map from a holder pointer to a pointer to an entry in a vector.
131 EntryMap mJSHolderMap
;
133 // A vector of holders not associated with a particular zone or that can
134 // contain pointers to GC things in more than one zone.
135 EntryVector mAnyZoneJSHolders
;
137 // A map from a zone to a vector of holders that only contain pointers to GC
138 // things in that zone.
140 // Currently this will only contain wrapper cache wrappers since these are the
141 // only holders to pass a zone parameter through to AddJSHolder.
142 EntryVectorMap mPerZoneJSHolders
;
144 // Iterators can mutate the element vectors by removing stale elements. Allow
145 // at most one to exist at a time.
146 bool mHasIterator
= false;
149 // An iterator over an EntryVector that skips over removed entries and removes
150 // them from the map.
151 class JSHolderMap::EntryVectorIter
{
153 EntryVectorIter(JSHolderMap
& aMap
, EntryVector
& aVector
)
154 : mHolderMap(aMap
), mVector(aVector
), mIter(aVector
.Iter()) {
158 const EntryVector
& Vector() const { return mVector
; }
160 bool Done() const { return mIter
.Done(); }
161 const Entry
& Get() const { return mIter
.Get(); }
167 operator const Entry
*() const { return &Get(); }
168 const Entry
* operator->() const { return &Get(); }
172 friend class JSHolderMap::Iter
;
174 JSHolderMap
& mHolderMap
;
175 EntryVector
& mVector
;
176 EntryVector::IterImpl mIter
;
179 class JSHolderMap::Iter
{
181 explicit Iter(JSHolderMap
& aMap
, WhichHolders aWhich
= AllHolders
);
184 MOZ_RELEASE_ASSERT(mHolderMap
.mHasIterator
);
185 mHolderMap
.mHasIterator
= false;
188 bool Done() const { return mIter
.Done(); }
189 const Entry
& Get() const { return mIter
.Get(); }
195 // If the holders have been removed from the map while the iterator is live,
196 // then the iterator may point to a removed entry. Update the iterator to make
197 // sure it points to a valid entry or is done.
198 void UpdateForRemovals();
200 operator const Entry
*() const { return &Get(); }
201 const Entry
* operator->() const { return &Get(); }
203 JS::Zone
* Zone() const { return mZone
; }
208 JSHolderMap
& mHolderMap
;
209 Vector
<JS::Zone
*, 1, InfallibleAllocPolicy
> mZones
;
210 JS::Zone
* mZone
= nullptr;
211 EntryVectorIter mIter
;
214 class CycleCollectedJSRuntime
{
215 friend class JSGCThingParticipant
;
216 friend class JSZoneParticipant
;
217 friend class IncrementalFinalizeRunnable
;
218 friend class CycleCollectedJSContext
;
221 CycleCollectedJSRuntime(JSContext
* aMainContext
);
222 virtual ~CycleCollectedJSRuntime();
224 virtual void Shutdown(JSContext
* cx
);
226 size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf
) const;
227 void UnmarkSkippableJSHolders();
229 virtual void TraverseAdditionalNativeRoots(
230 nsCycleCollectionNoteRootCallback
& aCb
) {}
231 virtual void TraceAdditionalNativeGrayRoots(JSTracer
* aTracer
) {}
233 virtual void CustomGCCallback(JSGCStatus aStatus
) {}
234 virtual void CustomOutOfMemoryCallback() {}
236 CycleCollectedJSContext
* GetContext() { return mContext
; }
239 void DescribeGCThing(bool aIsMarked
, JS::GCCellPtr aThing
,
240 nsCycleCollectionTraversalCallback
& aCb
) const;
242 virtual bool DescribeCustomObjects(JSObject
* aObject
, const JSClass
* aClasp
,
243 char (&aName
)[72]) const {
244 return false; // We did nothing.
247 void NoteGCThingJSChildren(JS::GCCellPtr aThing
,
248 nsCycleCollectionTraversalCallback
& aCb
) const;
250 void NoteGCThingXPCOMChildren(const JSClass
* aClasp
, JSObject
* aObj
,
251 nsCycleCollectionTraversalCallback
& aCb
) const;
253 virtual bool NoteCustomGCThingXPCOMChildren(
254 const JSClass
* aClasp
, JSObject
* aObj
,
255 nsCycleCollectionTraversalCallback
& aCb
) const {
256 return false; // We did nothing.
259 enum TraverseSelect
{ TRAVERSE_CPP
, TRAVERSE_FULL
};
261 void TraverseGCThing(TraverseSelect aTs
, JS::GCCellPtr aThing
,
262 nsCycleCollectionTraversalCallback
& aCb
);
264 void TraverseZone(JS::Zone
* aZone
, nsCycleCollectionTraversalCallback
& aCb
);
266 static void TraverseObjectShim(void* aData
, JS::GCCellPtr aThing
,
267 const JS::AutoRequireNoGC
& nogc
);
269 void TraverseNativeRoots(nsCycleCollectionNoteRootCallback
& aCb
);
271 static void TraceBlackJS(JSTracer
* aTracer
, void* aData
);
273 // Trace gray JS roots until budget is exceeded and return whether we
275 static bool TraceGrayJS(JSTracer
* aTracer
, JS::SliceBudget
& budget
,
278 static void GCCallback(JSContext
* aContext
, JSGCStatus aStatus
,
279 JS::GCReason aReason
, void* aData
);
280 static void GCSliceCallback(JSContext
* aContext
, JS::GCProgress aProgress
,
281 const JS::GCDescription
& aDesc
);
282 static void GCNurseryCollectionCallback(JSContext
* aContext
,
283 JS::GCNurseryProgress aProgress
,
284 JS::GCReason aReason
, void* data
);
285 static void OutOfMemoryCallback(JSContext
* aContext
, void* aData
);
287 static bool ContextCallback(JSContext
* aCx
, unsigned aOperation
, void* aData
);
289 static void* BeforeWaitCallback(uint8_t* aMemory
);
290 static void AfterWaitCallback(void* aCookie
);
292 virtual void TraceNativeBlackRoots(JSTracer
* aTracer
) {};
294 #ifdef NS_BUILD_REFCNT_LOGGING
295 void TraceAllNativeGrayRoots(JSTracer
* aTracer
);
298 bool TraceNativeGrayRoots(JSTracer
* aTracer
, JSHolderMap::WhichHolders aWhich
,
299 JS::SliceBudget
& aBudget
);
300 bool TraceJSHolders(JSTracer
* aTracer
, JSHolderMap::Iter
& aIter
,
301 JS::SliceBudget
& aBudget
);
304 enum DeferredFinalizeType
{
305 // Never finalize immediately, because it would be unsafe.
307 // Finalize later if we can, but it is okay to do it immediately.
308 FinalizeIncrementally
,
309 // Finalize immediately, for shutdown or testing purposes.
313 void FinalizeDeferredThings(DeferredFinalizeType aType
);
315 virtual void PrepareForForgetSkippable() = 0;
316 virtual void BeginCycleCollectionCallback(mozilla::CCReason aReason
) = 0;
317 virtual void EndCycleCollectionCallback(CycleCollectorResults
& aResults
) = 0;
318 virtual void DispatchDeferredDeletion(bool aContinuation
,
319 bool aPurge
= false) = 0;
321 // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in
322 // crash reports. Here are the values that can appear in the reports:
323 enum class OOMState
: uint32_t {
324 // The condition has never happened. No entry appears in the crash report.
327 // We are currently reporting the given condition.
329 // Suppose a crash report contains "JSLargeAllocationFailure:
330 // Reporting". This means we crashed while executing memory-pressure
331 // observers, trying to shake loose some memory. The large allocation in
332 // question did not return null: it is still on the stack. Had we not
333 // crashed, it would have been retried.
336 // The condition has been reported since the last GC.
338 // If a crash report contains "JSOutOfMemory: Reported", that means a small
339 // allocation failed, and then we crashed, probably due to buggy
340 // error-handling code that ran after allocation returned null.
342 // This contrasts with "Reporting" which means that no error-handling code
346 // The condition has happened, but a GC cycle ended since then.
348 // GC is taken as a proxy for "we've been banging on the heap a good bit
349 // now and haven't crashed; the OOM was probably handled correctly".
353 const char* OOMStateToString(const OOMState aOomState
) const;
355 // Returns true if OOM was reported and a new successful GC cycle hasn't
359 void SetLargeAllocationFailure(OOMState aNewState
);
361 void AnnotateAndSetOutOfMemory(OOMState
* aStatePtr
, OOMState aNewState
);
362 void OnGC(JSContext
* aContext
, JSGCStatus aStatus
, JS::GCReason aReason
);
363 void OnOutOfMemory();
364 void OnLargeAllocationFailure();
366 JSRuntime
* Runtime() { return mJSRuntime
; }
367 const JSRuntime
* Runtime() const { return mJSRuntime
; }
369 bool HasPendingIdleGCTask() const {
370 // Idle GC task associates with JSRuntime.
371 MOZ_ASSERT_IF(mHasPendingIdleGCTask
, Runtime());
372 return mHasPendingIdleGCTask
;
374 void SetPendingIdleGCTask() {
375 // Idle GC task associates with JSRuntime.
376 MOZ_ASSERT(Runtime());
377 mHasPendingIdleGCTask
= true;
379 void ClearPendingIdleGCTask() { mHasPendingIdleGCTask
= false; }
381 void RunIdleTimeGCTask() {
382 if (HasPendingIdleGCTask()) {
383 JS::MaybeRunNurseryCollection(Runtime(),
384 JS::GCReason::EAGER_NURSERY_COLLECTION
);
385 ClearPendingIdleGCTask();
389 bool IsIdleGCTaskNeeded() {
390 return !HasPendingIdleGCTask() && Runtime() &&
391 JS::WantEagerMinorGC(Runtime()) != JS::GCReason::NO_REASON
;
395 void AddJSHolder(void* aHolder
, nsScriptObjectTracer
* aTracer
,
397 void RemoveJSHolder(void* aHolder
);
399 void AssertNoObjectsToTrace(void* aPossibleJSHolder
);
402 nsCycleCollectionParticipant
* GCThingParticipant();
403 nsCycleCollectionParticipant
* ZoneParticipant();
405 nsresult
TraverseRoots(nsCycleCollectionNoteRootCallback
& aCb
);
406 virtual bool UsefulToMergeZones() const;
407 void FixWeakMappingGrayBits() const;
408 void CheckGrayBits() const;
409 bool AreGCGrayBitsValid() const;
410 void GarbageCollect(JS::GCOptions options
, JS::GCReason aReason
) const;
412 // This needs to be an nsWrapperCache, not a JSObject, because we need to know
413 // when our object gets moved. But we can't trace it (and hence update our
414 // storage), because we do not want to keep it alive. nsWrapperCache handles
415 // this for us via its "object moved" handling.
416 void NurseryWrapperAdded(nsWrapperCache
* aCache
);
417 void JSObjectsTenured(JS::GCContext
* aGCContext
);
419 void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc
,
420 DeferredFinalizeFunction aFunc
, void* aThing
);
421 void DeferredFinalize(nsISupports
* aSupports
);
423 void DumpJSHeap(FILE* aFile
);
425 // Add aZone to the set of zones waiting for a GC.
426 void AddZoneWaitingForGC(JS::Zone
* aZone
) {
427 mZonesWaitingForGC
.Insert(aZone
);
430 static void OnZoneDestroyed(JS::GCContext
* aGcx
, JS::Zone
* aZone
);
432 // Prepare any zones for GC that have been passed to AddZoneWaitingForGC()
433 // since the last GC or since the last call to PrepareWaitingZonesForGC(),
434 // whichever was most recent. If there were no such zones, prepare for a
436 void PrepareWaitingZonesForGC();
438 // Get the current thread's CycleCollectedJSRuntime. Returns null if there
440 static CycleCollectedJSRuntime
* Get();
442 void SetContext(CycleCollectedJSContext
* aContext
);
445 bool GetRecentDevError(JSContext
* aContext
,
446 JS::MutableHandle
<JS::Value
> aError
);
447 void ClearRecentDevError();
448 #endif // defined(NIGHTLY_BUILD)
451 CycleCollectedJSContext
* mContext
;
453 JSGCThingParticipant mGCThingCycleCollectorGlobal
;
455 JSZoneParticipant mJSZoneCycleCollectorGlobal
;
457 JSRuntime
* mJSRuntime
;
458 bool mHasPendingIdleGCTask
;
460 JS::GCSliceCallback mPrevGCSliceCallback
;
462 mozilla::TimeStamp mLatestNurseryCollectionStart
;
464 JSHolderMap mJSHolders
;
465 Maybe
<JSHolderMap::Iter
> mHolderIter
;
467 using DeferredFinalizerTable
= nsTHashMap
<DeferredFinalizeFunction
, void*>;
468 DeferredFinalizerTable mDeferredFinalizerTable
;
470 RefPtr
<IncrementalFinalizeRunnable
> mFinalizeRunnable
;
472 OOMState mOutOfMemoryState
;
473 OOMState mLargeAllocationFailureState
;
475 static const size_t kSegmentSize
= 512;
476 using NurseryObjectsVector
=
477 SegmentedVector
<nsWrapperCache
*, kSegmentSize
, InfallibleAllocPolicy
>;
478 NurseryObjectsVector mNurseryObjects
;
480 nsTHashSet
<JS::Zone
*> mZonesWaitingForGC
;
482 struct EnvironmentPreparer
: public js::ScriptEnvironmentPreparer
{
483 void invoke(JS::Handle
<JSObject
*> global
, Closure
& closure
) override
;
485 EnvironmentPreparer mEnvironmentPreparer
;
488 bool mShutdownCalled
;
492 // Implementation of the error interceptor.
493 // Built on nightly only to avoid any possible performance impact on release
495 struct ErrorInterceptor final
: public JSErrorInterceptor
{
496 virtual void interceptError(JSContext
* cx
,
497 JS::Handle
<JS::Value
> exn
) override
;
498 void Shutdown(JSRuntime
* rt
);
500 // Copy of the details of the exception.
501 // We store this rather than the exception itself to avoid dealing with
502 // complicated garbage-collection scenarios, e.g. a JSContext being killed
503 // while we still hold onto an exception thrown from it.
504 struct ErrorDetails
{
513 // If we have encountered at least one developer error,
514 // the first error we have encountered. Otherwise, or
515 // if we have reset since the latest error, `None`.
516 Maybe
<ErrorDetails
> mThrownError
;
518 ErrorInterceptor mErrorInterceptor
;
520 #endif // defined(NIGHTLY_BUILD)
523 void TraceScriptHolder(nsISupports
* aHolder
, JSTracer
* aTracer
);
525 } // namespace mozilla
527 #endif // mozilla_CycleCollectedJSRuntime_h