/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 js_GCAPI_h #define js_GCAPI_h #include "mozilla/UniquePtr.h" #include "mozilla/Vector.h" #include "js/HeapAPI.h" namespace js { namespace gc { class GCRuntime; } namespace gcstats { struct Statistics; } } typedef enum JSGCMode { /* Perform only global GCs. */ JSGC_MODE_GLOBAL = 0, /* Perform per-compartment GCs until too much garbage has accumulated. */ JSGC_MODE_COMPARTMENT = 1, /* * Collect in short time slices rather than all at once. Implies * JSGC_MODE_COMPARTMENT. */ JSGC_MODE_INCREMENTAL = 2 } JSGCMode; /* * Kinds of js_GC invocation. */ typedef enum JSGCInvocationKind { /* Normal invocation. */ GC_NORMAL = 0, /* Minimize GC triggers and release empty GC chunks right away. */ GC_SHRINK = 1 } JSGCInvocationKind; namespace JS { using mozilla::UniquePtr; #define GCREASONS(D) \ /* Reasons internal to the JS engine */ \ D(API) \ D(EAGER_ALLOC_TRIGGER) \ D(DESTROY_RUNTIME) \ D(DESTROY_CONTEXT) \ D(LAST_DITCH) \ D(TOO_MUCH_MALLOC) \ D(ALLOC_TRIGGER) \ D(DEBUG_GC) \ D(COMPARTMENT_REVIVED) \ D(RESET) \ D(OUT_OF_NURSERY) \ D(EVICT_NURSERY) \ D(FULL_STORE_BUFFER) \ D(SHARED_MEMORY_LIMIT) \ D(PERIODIC_FULL_GC) \ D(INCREMENTAL_TOO_SLOW) \ D(ABORT_GC) \ \ /* These are reserved for future use. */ \ D(RESERVED0) \ D(RESERVED1) \ D(RESERVED2) \ D(RESERVED3) \ D(RESERVED4) \ D(RESERVED5) \ D(RESERVED6) \ D(RESERVED7) \ D(RESERVED8) \ D(RESERVED9) \ D(RESERVED10) \ D(RESERVED11) \ D(RESERVED12) \ D(RESERVED13) \ D(RESERVED14) \ D(RESERVED15) \ \ /* Reasons from Firefox */ \ D(DOM_WINDOW_UTILS) \ D(COMPONENT_UTILS) \ D(MEM_PRESSURE) \ D(CC_WAITING) \ D(CC_FORCED) \ D(LOAD_END) \ D(POST_COMPARTMENT) \ D(PAGE_HIDE) \ D(NSJSCONTEXT_DESTROY) \ D(SET_NEW_DOCUMENT) \ D(SET_DOC_SHELL) \ D(DOM_UTILS) \ D(DOM_IPC) \ D(DOM_WORKER) \ D(INTER_SLICE_GC) \ D(REFRESH_FRAME) \ D(FULL_GC_TIMER) \ D(SHUTDOWN_CC) \ D(FINISH_LARGE_EVALUATE) \ D(USER_INACTIVE) \ D(XPCONNECT_SHUTDOWN) namespace gcreason { /* GCReasons will end up looking like JSGC_MAYBEGC */ enum Reason { #define MAKE_REASON(name) name, GCREASONS(MAKE_REASON) #undef MAKE_REASON NO_REASON, NUM_REASONS, /* * For telemetry, we want to keep a fixed max bucket size over time so we * don't have to switch histograms. 100 is conservative; as of this writing * there are 52. But the cost of extra buckets seems to be low while the * cost of switching histograms is high. */ NUM_TELEMETRY_REASONS = 100 }; } /* namespace gcreason */ /* * Zone GC: * * SpiderMonkey's GC is capable of performing a collection on an arbitrary * subset of the zones in the system. This allows an embedding to minimize * collection time by only collecting zones that have run code recently, * ignoring the parts of the heap that are unlikely to have changed. * * When triggering a GC using one of the functions below, it is first necessary * to select the zones to be collected. To do this, you can call * PrepareZoneForGC on each zone, or you can call PrepareForFullGC to select * all zones. Failing to select any zone is an error. */ /* * Schedule the given zone to be collected as part of the next GC. */ extern JS_PUBLIC_API(void) PrepareZoneForGC(Zone* zone); /* * Schedule all zones to be collected in the next GC. */ extern JS_PUBLIC_API(void) PrepareForFullGC(JSRuntime* rt); /* * When performing an incremental GC, the zones that were selected for the * previous incremental slice must be selected in subsequent slices as well. * This function selects those slices automatically. */ extern JS_PUBLIC_API(void) PrepareForIncrementalGC(JSRuntime* rt); /* * Returns true if any zone in the system has been scheduled for GC with one of * the functions above or by the JS engine. */ extern JS_PUBLIC_API(bool) IsGCScheduled(JSRuntime* rt); /* * Undoes the effect of the Prepare methods above. The given zone will not be * collected in the next GC. */ extern JS_PUBLIC_API(void) SkipZoneForGC(Zone* zone); /* * Non-Incremental GC: * * The following functions perform a non-incremental GC. */ /* * Performs a non-incremental collection of all selected zones. * * If the gckind argument is GC_NORMAL, then some objects that are unreachable * from the program may still be alive afterwards because of internal * references; if GC_SHRINK is passed then caches and other temporary references * to objects will be cleared and all unreferenced objects will be removed from * the system. */ extern JS_PUBLIC_API(void) GCForReason(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason reason); /* * Incremental GC: * * Incremental GC divides the full mark-and-sweep collection into multiple * slices, allowing client JavaScript code to run between each slice. This * allows interactive apps to avoid long collection pauses. Incremental GC does * not make collection take less time, it merely spreads that time out so that * the pauses are less noticable. * * For a collection to be carried out incrementally the following conditions * must be met: * - The collection must be run by calling JS::IncrementalGC() rather than * JS_GC(). * - The GC mode must have been set to JSGC_MODE_INCREMENTAL with * JS_SetGCParameter(). * - All native objects that have their own trace hook must indicate that they * implement read and write barriers with the JSCLASS_IMPLEMENTS_BARRIERS * flag. * * Note: Even if incremental GC is enabled and working correctly, * non-incremental collections can still happen when low on memory. */ /* * Begin an incremental collection and perform one slice worth of work. When * this function returns, the collection may not be complete. * IncrementalGCSlice() must be called repeatedly until * !IsIncrementalGCInProgress(rt). * * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or * shorter than the requested interval. */ extern JS_PUBLIC_API(void) StartIncrementalGC(JSRuntime* rt, JSGCInvocationKind gckind, gcreason::Reason reason, int64_t millis = 0); /* * Perform a slice of an ongoing incremental collection. When this function * returns, the collection may not be complete. It must be called repeatedly * until !IsIncrementalGCInProgress(rt). * * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or * shorter than the requested interval. */ extern JS_PUBLIC_API(void) IncrementalGCSlice(JSRuntime* rt, gcreason::Reason reason, int64_t millis = 0); /* * If IsIncrementalGCInProgress(rt), this call finishes the ongoing collection * by performing an arbitrarily long slice. If !IsIncrementalGCInProgress(rt), * this is equivalent to GCForReason. When this function returns, * IsIncrementalGCInProgress(rt) will always be false. */ extern JS_PUBLIC_API(void) FinishIncrementalGC(JSRuntime* rt, gcreason::Reason reason); /* * If IsIncrementalGCInProgress(rt), this call aborts the ongoing collection and * performs whatever work needs to be done to return the collector to its idle * state. This may take an arbitrarily long time. When this function returns, * IsIncrementalGCInProgress(rt) will always be false. */ extern JS_PUBLIC_API(void) AbortIncrementalGC(JSRuntime* rt); namespace dbg { // The `JS::dbg::GarbageCollectionEvent` class is essentially a view of the // `js::gcstats::Statistics` data without the uber implementation-specific bits. // It should generally be palatable for web developers. class GarbageCollectionEvent { // The major GC number of the GC cycle this data pertains to. uint64_t majorGCNumber_; // Reference to a non-owned, statically allocated C string. This is a very // short reason explaining why a GC was triggered. const char* reason; // Reference to a nullable, non-owned, statically allocated C string. If the // collection was forced to be non-incremental, this is a short reason of // why the GC could not perform an incremental collection. const char* nonincrementalReason; // Represents a single slice of a possibly multi-slice incremental garbage // collection. struct Collection { double startTimestamp; double endTimestamp; }; // The set of garbage collection slices that made up this GC cycle. mozilla::Vector collections; GarbageCollectionEvent(const GarbageCollectionEvent& rhs) = delete; GarbageCollectionEvent& operator=(const GarbageCollectionEvent& rhs) = delete; public: explicit GarbageCollectionEvent(uint64_t majorGCNum) : majorGCNumber_(majorGCNum) , reason(nullptr) , nonincrementalReason(nullptr) , collections() { } using Ptr = UniquePtr>; static Ptr Create(JSRuntime* rt, ::js::gcstats::Statistics& stats, uint64_t majorGCNumber); JSObject* toJSObject(JSContext* cx) const; uint64_t majorGCNumber() const { return majorGCNumber_; } }; } // namespace dbg enum GCProgress { /* * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END * callbacks. During an incremental GC, the sequence of callbacks is as * follows: * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice) * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice) * ... * JSGC_SLICE_BEGIN, JSGC_CYCLE_END (last slice) */ GC_CYCLE_BEGIN, GC_SLICE_BEGIN, GC_SLICE_END, GC_CYCLE_END }; struct JS_PUBLIC_API(GCDescription) { bool isCompartment_; JSGCInvocationKind invocationKind_; gcreason::Reason reason_; GCDescription(bool isCompartment, JSGCInvocationKind kind, gcreason::Reason reason) : isCompartment_(isCompartment), invocationKind_(kind), reason_(reason) {} char16_t* formatSliceMessage(JSRuntime* rt) const; char16_t* formatSummaryMessage(JSRuntime* rt) const; char16_t* formatJSON(JSRuntime* rt, uint64_t timestamp) const; JS::dbg::GarbageCollectionEvent::Ptr toGCEvent(JSRuntime* rt) const; }; typedef void (* GCSliceCallback)(JSRuntime* rt, GCProgress progress, const GCDescription& desc); /* * The GC slice callback is called at the beginning and end of each slice. This * callback may be used for GC notifications as well as to perform additional * marking. */ extern JS_PUBLIC_API(GCSliceCallback) SetGCSliceCallback(JSRuntime* rt, GCSliceCallback callback); /* * Incremental GC defaults to enabled, but may be disabled for testing or in * embeddings that have not yet implemented barriers on their native classes. * There is not currently a way to re-enable incremental GC once it has been * disabled on the runtime. */ extern JS_PUBLIC_API(void) DisableIncrementalGC(JSRuntime* rt); /* * Returns true if incremental GC is enabled. Simply having incremental GC * enabled is not sufficient to ensure incremental collections are happening. * See the comment "Incremental GC" above for reasons why incremental GC may be * suppressed. Inspection of the "nonincremental reason" field of the * GCDescription returned by GCSliceCallback may help narrow down the cause if * collections are not happening incrementally when expected. */ extern JS_PUBLIC_API(bool) IsIncrementalGCEnabled(JSRuntime* rt); /* * Returns true while an incremental GC is ongoing, both when actively * collecting and between slices. */ extern JS_PUBLIC_API(bool) IsIncrementalGCInProgress(JSRuntime* rt); /* * Returns true when writes to GC things must call an incremental (pre) barrier. * This is generally only true when running mutator code in-between GC slices. * At other times, the barrier may be elided for performance. */ extern JS_PUBLIC_API(bool) IsIncrementalBarrierNeeded(JSRuntime* rt); extern JS_PUBLIC_API(bool) IsIncrementalBarrierNeeded(JSContext* cx); /* * Notify the GC that a reference to a GC thing is about to be overwritten. * These methods must be called if IsIncrementalBarrierNeeded. */ extern JS_PUBLIC_API(void) IncrementalReferenceBarrier(GCCellPtr thing); extern JS_PUBLIC_API(void) IncrementalValueBarrier(const Value& v); extern JS_PUBLIC_API(void) IncrementalObjectBarrier(JSObject* obj); /* * Returns true if the most recent GC ran incrementally. */ extern JS_PUBLIC_API(bool) WasIncrementalGC(JSRuntime* rt); /* * Generational GC: * * Note: Generational GC is not yet enabled by default. The following class * is non-functional unless SpiderMonkey was configured with * --enable-gcgenerational. */ /* Ensure that generational GC is disabled within some scope. */ class JS_PUBLIC_API(AutoDisableGenerationalGC) { js::gc::GCRuntime* gc; public: explicit AutoDisableGenerationalGC(JSRuntime* rt); ~AutoDisableGenerationalGC(); }; /* * Returns true if generational allocation and collection is currently enabled * on the given runtime. */ extern JS_PUBLIC_API(bool) IsGenerationalGCEnabled(JSRuntime* rt); /* * Returns the GC's "number". This does not correspond directly to the number * of GCs that have been run, but is guaranteed to be monotonically increasing * with GC activity. */ extern JS_PUBLIC_API(size_t) GetGCNumber(); /* * The GC does not immediately return the unused memory freed by a collection * back to the system incase it is needed soon afterwards. This call forces the * GC to return this memory immediately. */ extern JS_PUBLIC_API(void) ShrinkGCBuffers(JSRuntime* rt); /* * Assert if a GC occurs while this class is live. This class does not disable * the static rooting hazard analysis. */ class JS_PUBLIC_API(AutoAssertOnGC) { #ifdef DEBUG js::gc::GCRuntime* gc; size_t gcNumber; public: AutoAssertOnGC(); explicit AutoAssertOnGC(JSRuntime* rt); ~AutoAssertOnGC(); static void VerifyIsSafeToGC(JSRuntime* rt); #else public: AutoAssertOnGC() {} explicit AutoAssertOnGC(JSRuntime* rt) {} ~AutoAssertOnGC() {} static void VerifyIsSafeToGC(JSRuntime* rt) {} #endif }; /* * Assert if an allocation of a GC thing occurs while this class is live. This * class does not disable the static rooting hazard analysis. */ class JS_PUBLIC_API(AutoAssertNoAlloc) { #ifdef JS_DEBUG js::gc::GCRuntime* gc; public: AutoAssertNoAlloc() : gc(nullptr) {} explicit AutoAssertNoAlloc(JSRuntime* rt); void disallowAlloc(JSRuntime* rt); ~AutoAssertNoAlloc(); #else public: AutoAssertNoAlloc() {} explicit AutoAssertNoAlloc(JSRuntime* rt) {} void disallowAlloc(JSRuntime* rt) {} #endif }; /* * Disable the static rooting hazard analysis in the live region and assert if * any allocation that could potentially trigger a GC occurs while this guard * object is live. This is most useful to help the exact rooting hazard analysis * in complex regions, since it cannot understand dataflow. * * Note: GC behavior is unpredictable even when deterministic and is generally * non-deterministic in practice. The fact that this guard has not * asserted is not a guarantee that a GC cannot happen in the guarded * region. As a rule, anyone performing a GC unsafe action should * understand the GC properties of all code in that region and ensure * that the hazard analysis is correct for that code, rather than relying * on this class. */ class JS_PUBLIC_API(AutoSuppressGCAnalysis) : public AutoAssertNoAlloc { public: AutoSuppressGCAnalysis() : AutoAssertNoAlloc() {} explicit AutoSuppressGCAnalysis(JSRuntime* rt) : AutoAssertNoAlloc(rt) {} }; /* * Assert that code is only ever called from a GC callback, disable the static * rooting hazard analysis and assert if any allocation that could potentially * trigger a GC occurs while this guard object is live. * * This is useful to make the static analysis ignore code that runs in GC * callbacks. */ class JS_PUBLIC_API(AutoAssertGCCallback) : public AutoSuppressGCAnalysis { public: explicit AutoAssertGCCallback(JSObject* obj); }; /* * Place AutoCheckCannotGC in scopes that you believe can never GC. These * annotations will be verified both dynamically via AutoAssertOnGC, and * statically with the rooting hazard analysis (implemented by making the * analysis consider AutoCheckCannotGC to be a GC pointer, and therefore * complain if it is live across a GC call.) It is useful when dealing with * internal pointers to GC things where the GC thing itself may not be present * for the static analysis: e.g. acquiring inline chars from a JSString* on the * heap. */ class JS_PUBLIC_API(AutoCheckCannotGC) : public AutoAssertOnGC { public: AutoCheckCannotGC() : AutoAssertOnGC() {} explicit AutoCheckCannotGC(JSRuntime* rt) : AutoAssertOnGC(rt) {} }; /* * Unsets the gray bit for anything reachable from |thing|. |kind| should not be * JS::TraceKind::Shape. |thing| should be non-null. */ extern JS_FRIEND_API(bool) UnmarkGrayGCThingRecursively(GCCellPtr thing); } /* namespace JS */ namespace js { namespace gc { static MOZ_ALWAYS_INLINE void ExposeGCThingToActiveJS(JS::GCCellPtr thing) { MOZ_ASSERT(thing.kind() != JS::TraceKind::Shape); /* * GC things residing in the nursery cannot be gray: they have no mark bits. * All live objects in the nursery are moved to tenured at the beginning of * each GC slice, so the gray marker never sees nursery things. */ if (IsInsideNursery(thing.asCell())) return; JS::shadow::Runtime* rt = detail::GetGCThingRuntime(thing.unsafeAsUIntPtr()); if (IsIncrementalBarrierNeededOnTenuredGCThing(rt, thing)) JS::IncrementalReferenceBarrier(thing); else if (JS::GCThingIsMarkedGray(thing)) JS::UnmarkGrayGCThingRecursively(thing); } static MOZ_ALWAYS_INLINE void MarkGCThingAsLive(JSRuntime* aRt, JS::GCCellPtr thing) { JS::shadow::Runtime* rt = JS::shadow::Runtime::asShadowRuntime(aRt); /* * Any object in the nursery will not be freed during any GC running at that time. */ if (IsInsideNursery(thing.asCell())) return; if (IsIncrementalBarrierNeededOnTenuredGCThing(rt, thing)) JS::IncrementalReferenceBarrier(thing); } } /* namespace gc */ } /* namespace js */ namespace JS { /* * This should be called when an object that is marked gray is exposed to the JS * engine (by handing it to running JS code or writing it into live JS * data). During incremental GC, since the gray bits haven't been computed yet, * we conservatively mark the object black. */ static MOZ_ALWAYS_INLINE void ExposeObjectToActiveJS(JSObject* obj) { js::gc::ExposeGCThingToActiveJS(GCCellPtr(obj)); } static MOZ_ALWAYS_INLINE void ExposeScriptToActiveJS(JSScript* script) { js::gc::ExposeGCThingToActiveJS(GCCellPtr(script)); } /* * If a GC is currently marking, mark the string black. */ static MOZ_ALWAYS_INLINE void MarkStringAsLive(Zone* zone, JSString* string) { JSRuntime* rt = JS::shadow::Zone::asShadowZone(zone)->runtimeFromMainThread(); js::gc::MarkGCThingAsLive(rt, GCCellPtr(string)); } /* * Internal to Firefox. * * Note: this is not related to the PokeGC in nsJSEnvironment. */ extern JS_FRIEND_API(void) PokeGC(JSRuntime* rt); /* * Internal to Firefox. */ extern JS_FRIEND_API(void) NotifyDidPaint(JSRuntime* rt); } /* namespace JS */ #endif /* js_GCAPI_h */