michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef js_GCAPI_h michael@0: #define js_GCAPI_h michael@0: michael@0: #include "mozilla/NullPtr.h" michael@0: michael@0: #include "js/HeapAPI.h" michael@0: #include "js/RootingAPI.h" michael@0: #include "js/Value.h" michael@0: michael@0: typedef enum JSGCMode { michael@0: /* Perform only global GCs. */ michael@0: JSGC_MODE_GLOBAL = 0, michael@0: michael@0: /* Perform per-compartment GCs until too much garbage has accumulated. */ michael@0: JSGC_MODE_COMPARTMENT = 1, michael@0: michael@0: /* michael@0: * Collect in short time slices rather than all at once. Implies michael@0: * JSGC_MODE_COMPARTMENT. michael@0: */ michael@0: JSGC_MODE_INCREMENTAL = 2 michael@0: } JSGCMode; michael@0: michael@0: namespace JS { michael@0: michael@0: #define GCREASONS(D) \ michael@0: /* Reasons internal to the JS engine */ \ michael@0: D(API) \ michael@0: D(MAYBEGC) \ michael@0: D(DESTROY_RUNTIME) \ michael@0: D(DESTROY_CONTEXT) \ michael@0: D(LAST_DITCH) \ michael@0: D(TOO_MUCH_MALLOC) \ michael@0: D(ALLOC_TRIGGER) \ michael@0: D(DEBUG_GC) \ michael@0: D(TRANSPLANT) \ michael@0: D(RESET) \ michael@0: D(OUT_OF_NURSERY) \ michael@0: D(EVICT_NURSERY) \ michael@0: D(FULL_STORE_BUFFER) \ michael@0: \ michael@0: /* These are reserved for future use. */ \ michael@0: D(RESERVED0) \ michael@0: D(RESERVED1) \ michael@0: D(RESERVED2) \ michael@0: D(RESERVED3) \ michael@0: D(RESERVED4) \ michael@0: D(RESERVED5) \ michael@0: D(RESERVED6) \ michael@0: D(RESERVED7) \ michael@0: D(RESERVED8) \ michael@0: D(RESERVED9) \ michael@0: D(RESERVED10) \ michael@0: D(RESERVED11) \ michael@0: D(RESERVED12) \ michael@0: D(RESERVED13) \ michael@0: D(RESERVED14) \ michael@0: D(RESERVED15) \ michael@0: D(RESERVED16) \ michael@0: D(RESERVED17) \ michael@0: D(RESERVED18) \ michael@0: D(RESERVED19) \ michael@0: \ michael@0: /* Reasons from Firefox */ \ michael@0: D(DOM_WINDOW_UTILS) \ michael@0: D(COMPONENT_UTILS) \ michael@0: D(MEM_PRESSURE) \ michael@0: D(CC_WAITING) \ michael@0: D(CC_FORCED) \ michael@0: D(LOAD_END) \ michael@0: D(POST_COMPARTMENT) \ michael@0: D(PAGE_HIDE) \ michael@0: D(NSJSCONTEXT_DESTROY) \ michael@0: D(SET_NEW_DOCUMENT) \ michael@0: D(SET_DOC_SHELL) \ michael@0: D(DOM_UTILS) \ michael@0: D(DOM_IPC) \ michael@0: D(DOM_WORKER) \ michael@0: D(INTER_SLICE_GC) \ michael@0: D(REFRESH_FRAME) \ michael@0: D(FULL_GC_TIMER) \ michael@0: D(SHUTDOWN_CC) \ michael@0: D(FINISH_LARGE_EVALUTE) michael@0: michael@0: namespace gcreason { michael@0: michael@0: /* GCReasons will end up looking like JSGC_MAYBEGC */ michael@0: enum Reason { michael@0: #define MAKE_REASON(name) name, michael@0: GCREASONS(MAKE_REASON) michael@0: #undef MAKE_REASON michael@0: NO_REASON, michael@0: NUM_REASONS, michael@0: michael@0: /* michael@0: * For telemetry, we want to keep a fixed max bucket size over time so we michael@0: * don't have to switch histograms. 100 is conservative; as of this writing michael@0: * there are 26. But the cost of extra buckets seems to be low while the michael@0: * cost of switching histograms is high. michael@0: */ michael@0: NUM_TELEMETRY_REASONS = 100 michael@0: }; michael@0: michael@0: } /* namespace gcreason */ michael@0: michael@0: /* michael@0: * Zone GC: michael@0: * michael@0: * SpiderMonkey's GC is capable of performing a collection on an arbitrary michael@0: * subset of the zones in the system. This allows an embedding to minimize michael@0: * collection time by only collecting zones that have run code recently, michael@0: * ignoring the parts of the heap that are unlikely to have changed. michael@0: * michael@0: * When triggering a GC using one of the functions below, it is first necessary michael@0: * to select the zones to be collected. To do this, you can call michael@0: * PrepareZoneForGC on each zone, or you can call PrepareForFullGC to select michael@0: * all zones. Failing to select any zone is an error. michael@0: */ michael@0: michael@0: /* michael@0: * Schedule the given zone to be collected as part of the next GC. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: PrepareZoneForGC(Zone *zone); michael@0: michael@0: /* michael@0: * Schedule all zones to be collected in the next GC. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: PrepareForFullGC(JSRuntime *rt); michael@0: michael@0: /* michael@0: * When performing an incremental GC, the zones that were selected for the michael@0: * previous incremental slice must be selected in subsequent slices as well. michael@0: * This function selects those slices automatically. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: PrepareForIncrementalGC(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Returns true if any zone in the system has been scheduled for GC with one of michael@0: * the functions above or by the JS engine. michael@0: */ michael@0: extern JS_FRIEND_API(bool) michael@0: IsGCScheduled(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Undoes the effect of the Prepare methods above. The given zone will not be michael@0: * collected in the next GC. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: SkipZoneForGC(Zone *zone); michael@0: michael@0: /* michael@0: * Non-Incremental GC: michael@0: * michael@0: * The following functions perform a non-incremental GC. michael@0: */ michael@0: michael@0: /* michael@0: * Performs a non-incremental collection of all selected zones. Some objects michael@0: * that are unreachable from the program may still be alive afterwards because michael@0: * of internal references. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: GCForReason(JSRuntime *rt, gcreason::Reason reason); michael@0: michael@0: /* michael@0: * Perform a non-incremental collection after clearing caches and other michael@0: * temporary references to objects. This will remove all unreferenced objects michael@0: * in the system. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: ShrinkingGC(JSRuntime *rt, gcreason::Reason reason); michael@0: michael@0: /* michael@0: * Incremental GC: michael@0: * michael@0: * Incremental GC divides the full mark-and-sweep collection into multiple michael@0: * slices, allowing client JavaScript code to run between each slice. This michael@0: * allows interactive apps to avoid long collection pauses. Incremental GC does michael@0: * not make collection take less time, it merely spreads that time out so that michael@0: * the pauses are less noticable. michael@0: * michael@0: * For a collection to be carried out incrementally the following conditions michael@0: * must be met: michael@0: * - The collection must be run by calling JS::IncrementalGC() rather than michael@0: * JS_GC(). michael@0: * - The GC mode must have been set to JSGC_MODE_INCREMENTAL with michael@0: * JS_SetGCParameter(). michael@0: * - All native objects that have their own trace hook must indicate that they michael@0: * implement read and write barriers with the JSCLASS_IMPLEMENTS_BARRIERS michael@0: * flag. michael@0: * michael@0: * Note: Even if incremental GC is enabled and working correctly, michael@0: * non-incremental collections can still happen when low on memory. michael@0: */ michael@0: michael@0: /* michael@0: * Begin an incremental collection and perform one slice worth of work or michael@0: * perform a slice of an ongoing incremental collection. When this function michael@0: * returns, the collection is not complete. This function must be called michael@0: * repeatedly until !IsIncrementalGCInProgress(rt). michael@0: * michael@0: * Note: SpiderMonkey's GC is not realtime. Slices in practice may be longer or michael@0: * shorter than the requested interval. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: IncrementalGC(JSRuntime *rt, gcreason::Reason reason, int64_t millis = 0); michael@0: michael@0: /* michael@0: * If IsIncrementalGCInProgress(rt), this call finishes the ongoing collection michael@0: * by performing an arbitrarily long slice. If !IsIncrementalGCInProgress(rt), michael@0: * this is equivalent to GCForReason. When this function returns, michael@0: * IsIncrementalGCInProgress(rt) will always be false. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: FinishIncrementalGC(JSRuntime *rt, gcreason::Reason reason); michael@0: michael@0: enum GCProgress { michael@0: /* michael@0: * During non-incremental GC, the GC is bracketed by JSGC_CYCLE_BEGIN/END michael@0: * callbacks. During an incremental GC, the sequence of callbacks is as michael@0: * follows: michael@0: * JSGC_CYCLE_BEGIN, JSGC_SLICE_END (first slice) michael@0: * JSGC_SLICE_BEGIN, JSGC_SLICE_END (second slice) michael@0: * ... michael@0: * JSGC_SLICE_BEGIN, JSGC_CYCLE_END (last slice) michael@0: */ michael@0: michael@0: GC_CYCLE_BEGIN, michael@0: GC_SLICE_BEGIN, michael@0: GC_SLICE_END, michael@0: GC_CYCLE_END michael@0: }; michael@0: michael@0: struct JS_FRIEND_API(GCDescription) { michael@0: bool isCompartment_; michael@0: michael@0: GCDescription(bool isCompartment) michael@0: : isCompartment_(isCompartment) {} michael@0: michael@0: jschar *formatMessage(JSRuntime *rt) const; michael@0: jschar *formatJSON(JSRuntime *rt, uint64_t timestamp) const; michael@0: }; michael@0: michael@0: typedef void michael@0: (* GCSliceCallback)(JSRuntime *rt, GCProgress progress, const GCDescription &desc); michael@0: michael@0: /* michael@0: * The GC slice callback is called at the beginning and end of each slice. This michael@0: * callback may be used for GC notifications as well as to perform additional michael@0: * marking. michael@0: */ michael@0: extern JS_FRIEND_API(GCSliceCallback) michael@0: SetGCSliceCallback(JSRuntime *rt, GCSliceCallback callback); michael@0: michael@0: /* michael@0: * Incremental GC defaults to enabled, but may be disabled for testing or in michael@0: * embeddings that have not yet implemented barriers on their native classes. michael@0: * There is not currently a way to re-enable incremental GC once it has been michael@0: * disabled on the runtime. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: DisableIncrementalGC(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Returns true if incremental GC is enabled. Simply having incremental GC michael@0: * enabled is not sufficient to ensure incremental collections are happening. michael@0: * See the comment "Incremental GC" above for reasons why incremental GC may be michael@0: * suppressed. Inspection of the "nonincremental reason" field of the michael@0: * GCDescription returned by GCSliceCallback may help narrow down the cause if michael@0: * collections are not happening incrementally when expected. michael@0: */ michael@0: extern JS_FRIEND_API(bool) michael@0: IsIncrementalGCEnabled(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Returns true while an incremental GC is ongoing, both when actively michael@0: * collecting and between slices. michael@0: */ michael@0: JS_FRIEND_API(bool) michael@0: IsIncrementalGCInProgress(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Returns true when writes to GC things must call an incremental (pre) barrier. michael@0: * This is generally only true when running mutator code in-between GC slices. michael@0: * At other times, the barrier may be elided for performance. michael@0: */ michael@0: extern JS_FRIEND_API(bool) michael@0: IsIncrementalBarrierNeeded(JSRuntime *rt); michael@0: michael@0: extern JS_FRIEND_API(bool) michael@0: IsIncrementalBarrierNeeded(JSContext *cx); michael@0: michael@0: /* michael@0: * Notify the GC that a reference to a GC thing is about to be overwritten. michael@0: * These methods must be called if IsIncrementalBarrierNeeded. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: IncrementalReferenceBarrier(void *ptr, JSGCTraceKind kind); michael@0: michael@0: extern JS_FRIEND_API(void) michael@0: IncrementalValueBarrier(const Value &v); michael@0: michael@0: extern JS_FRIEND_API(void) michael@0: IncrementalObjectBarrier(JSObject *obj); michael@0: michael@0: /* michael@0: * Returns true if the most recent GC ran incrementally. michael@0: */ michael@0: extern JS_FRIEND_API(bool) michael@0: WasIncrementalGC(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Generational GC: michael@0: * michael@0: * Note: Generational GC is not yet enabled by default. The following class michael@0: * is non-functional unless SpiderMonkey was configured with michael@0: * --enable-gcgenerational. michael@0: */ michael@0: michael@0: /* Ensure that generational GC is disabled within some scope. */ michael@0: class JS_FRIEND_API(AutoDisableGenerationalGC) michael@0: { michael@0: JSRuntime *runtime; michael@0: #if defined(JSGC_GENERATIONAL) && defined(JS_GC_ZEAL) michael@0: bool restartVerifier; michael@0: #endif michael@0: michael@0: public: michael@0: AutoDisableGenerationalGC(JSRuntime *rt); michael@0: ~AutoDisableGenerationalGC(); michael@0: }; michael@0: michael@0: /* michael@0: * Returns true if generational allocation and collection is currently enabled michael@0: * on the given runtime. michael@0: */ michael@0: extern JS_FRIEND_API(bool) michael@0: IsGenerationalGCEnabled(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Returns the GC's "number". This does not correspond directly to the number michael@0: * of GCs that have been run, but is guaranteed to be monotonically increasing michael@0: * with GC activity. michael@0: */ michael@0: extern JS_FRIEND_API(size_t) michael@0: GetGCNumber(); michael@0: michael@0: /* michael@0: * The GC does not immediately return the unused memory freed by a collection michael@0: * back to the system incase it is needed soon afterwards. This call forces the michael@0: * GC to return this memory immediately. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: ShrinkGCBuffers(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Assert if any GC occured while this guard object was live. This is most michael@0: * useful to help the exact rooting hazard analysis in complex regions, since michael@0: * it cannot understand dataflow. michael@0: * michael@0: * Note: GC behavior is unpredictable even when deterministice and is generally michael@0: * non-deterministic in practice. The fact that this guard has not michael@0: * asserted is not a guarantee that a GC cannot happen in the guarded michael@0: * region. As a rule, anyone performing a GC unsafe action should michael@0: * understand the GC properties of all code in that region and ensure michael@0: * that the hazard analysis is correct for that code, rather than relying michael@0: * on this class. michael@0: */ michael@0: class JS_PUBLIC_API(AutoAssertNoGC) michael@0: { michael@0: #ifdef JS_DEBUG michael@0: JSRuntime *runtime; michael@0: size_t gcNumber; michael@0: michael@0: public: michael@0: AutoAssertNoGC(); michael@0: AutoAssertNoGC(JSRuntime *rt); michael@0: ~AutoAssertNoGC(); michael@0: #else michael@0: public: michael@0: /* Prevent unreferenced local warnings in opt builds. */ michael@0: AutoAssertNoGC() {} michael@0: AutoAssertNoGC(JSRuntime *) {} michael@0: #endif michael@0: }; michael@0: michael@0: class JS_PUBLIC_API(ObjectPtr) michael@0: { michael@0: Heap value; michael@0: michael@0: public: michael@0: ObjectPtr() : value(nullptr) {} michael@0: michael@0: ObjectPtr(JSObject *obj) : value(obj) {} michael@0: michael@0: /* Always call finalize before the destructor. */ michael@0: ~ObjectPtr() { MOZ_ASSERT(!value); } michael@0: michael@0: void finalize(JSRuntime *rt) { michael@0: if (IsIncrementalBarrierNeeded(rt)) michael@0: IncrementalObjectBarrier(value); michael@0: value = nullptr; michael@0: } michael@0: michael@0: void init(JSObject *obj) { value = obj; } michael@0: michael@0: JSObject *get() const { return value; } michael@0: michael@0: void writeBarrierPre(JSRuntime *rt) { michael@0: IncrementalObjectBarrier(value); michael@0: } michael@0: michael@0: bool isAboutToBeFinalized(); michael@0: michael@0: ObjectPtr &operator=(JSObject *obj) { michael@0: IncrementalObjectBarrier(value); michael@0: value = obj; michael@0: return *this; michael@0: } michael@0: michael@0: void trace(JSTracer *trc, const char *name); michael@0: michael@0: JSObject &operator*() const { return *value; } michael@0: JSObject *operator->() const { return value; } michael@0: operator JSObject *() const { return value; } michael@0: }; michael@0: michael@0: /* michael@0: * Unsets the gray bit for anything reachable from |thing|. |kind| should not be michael@0: * JSTRACE_SHAPE. |thing| should be non-null. michael@0: */ michael@0: extern JS_FRIEND_API(bool) michael@0: UnmarkGrayGCThingRecursively(void *thing, JSGCTraceKind kind); michael@0: michael@0: /* michael@0: * This should be called when an object that is marked gray is exposed to the JS michael@0: * engine (by handing it to running JS code or writing it into live JS michael@0: * data). During incremental GC, since the gray bits haven't been computed yet, michael@0: * we conservatively mark the object black. michael@0: */ michael@0: static MOZ_ALWAYS_INLINE void michael@0: ExposeGCThingToActiveJS(void *thing, JSGCTraceKind kind) michael@0: { michael@0: MOZ_ASSERT(kind != JSTRACE_SHAPE); michael@0: michael@0: shadow::Runtime *rt = js::gc::GetGCThingRuntime(thing); michael@0: #ifdef JSGC_GENERATIONAL michael@0: /* michael@0: * GC things residing in the nursery cannot be gray: they have no mark bits. michael@0: * All live objects in the nursery are moved to tenured at the beginning of michael@0: * each GC slice, so the gray marker never sees nursery things. michael@0: */ michael@0: if (js::gc::IsInsideNursery(rt, thing)) michael@0: return; michael@0: #endif michael@0: if (IsIncrementalBarrierNeededOnGCThing(rt, thing, kind)) michael@0: IncrementalReferenceBarrier(thing, kind); michael@0: else if (GCThingIsMarkedGray(thing)) michael@0: UnmarkGrayGCThingRecursively(thing, kind); michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: ExposeValueToActiveJS(const Value &v) michael@0: { michael@0: if (v.isMarkable()) michael@0: ExposeGCThingToActiveJS(v.toGCThing(), v.gcKind()); michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: ExposeObjectToActiveJS(JSObject *obj) michael@0: { michael@0: ExposeGCThingToActiveJS(obj, JSTRACE_OBJECT); michael@0: } michael@0: michael@0: /* michael@0: * If a GC is currently marking, mark the object black. michael@0: */ michael@0: static MOZ_ALWAYS_INLINE void michael@0: MarkGCThingAsLive(JSRuntime *rt_, void *thing, JSGCTraceKind kind) michael@0: { michael@0: shadow::Runtime *rt = shadow::Runtime::asShadowRuntime(rt_); michael@0: #ifdef JSGC_GENERATIONAL michael@0: /* michael@0: * Any object in the nursery will not be freed during any GC running at that time. michael@0: */ michael@0: if (js::gc::IsInsideNursery(rt, thing)) michael@0: return; michael@0: #endif michael@0: if (IsIncrementalBarrierNeededOnGCThing(rt, thing, kind)) michael@0: IncrementalReferenceBarrier(thing, kind); michael@0: } michael@0: michael@0: static MOZ_ALWAYS_INLINE void michael@0: MarkStringAsLive(Zone *zone, JSString *string) michael@0: { michael@0: JSRuntime *rt = JS::shadow::Zone::asShadowZone(zone)->runtimeFromMainThread(); michael@0: MarkGCThingAsLive(rt, string, JSTRACE_STRING); michael@0: } michael@0: michael@0: /* michael@0: * Internal to Firefox. michael@0: * michael@0: * Note: this is not related to the PokeGC in nsJSEnvironment. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: PokeGC(JSRuntime *rt); michael@0: michael@0: /* michael@0: * Internal to Firefox. michael@0: */ michael@0: extern JS_FRIEND_API(void) michael@0: NotifyDidPaint(JSRuntime *rt); michael@0: michael@0: } /* namespace JS */ michael@0: michael@0: #endif /* js_GCAPI_h */