/* 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/. */

/*
 * JS engine garbage collector API.
 */

#ifndef gc_GC_h
#define gc_GC_h

#include "gc/AllocKind.h"
#include "gc/GCEnum.h"
#include "js/Context.h"
#include "js/GCAPI.h"
#include "js/HeapAPI.h"
#include "js/RealmIterators.h"
#include "js/TraceKind.h"
#include "vm/GeckoProfiler.h"

class JSTracer;

namespace JS {
class RealmOptions;
}

namespace js {

class Nursery;

namespace gc {

class Arena;
class ArenaChunk;

} /* namespace gc */

// Define name, key and writability for the GC parameters.
#define FOR_EACH_GC_PARAM(_)                                                \
  _("maxBytes", JSGC_MAX_BYTES, true)                                       \
  _("minNurseryBytes", JSGC_MIN_NURSERY_BYTES, true)                        \
  _("maxNurseryBytes", JSGC_MAX_NURSERY_BYTES, true)                        \
  _("gcBytes", JSGC_BYTES, false)                                           \
  _("nurseryBytes", JSGC_NURSERY_BYTES, false)                              \
  _("gcNumber", JSGC_NUMBER, false)                                         \
  _("majorGCNumber", JSGC_MAJOR_GC_NUMBER, false)                           \
  _("minorGCNumber", JSGC_MINOR_GC_NUMBER, false)                           \
  _("sliceNumber", JSGC_SLICE_NUMBER, false)                                \
  _("incrementalGCEnabled", JSGC_INCREMENTAL_GC_ENABLED, true)              \
  _("perZoneGCEnabled", JSGC_PER_ZONE_GC_ENABLED, true)                     \
  _("unusedChunks", JSGC_UNUSED_CHUNKS, false)                              \
  _("totalChunks", JSGC_TOTAL_CHUNKS, false)                                \
  _("sliceTimeBudgetMS", JSGC_SLICE_TIME_BUDGET_MS, true)                   \
  _("highFrequencyTimeLimit", JSGC_HIGH_FREQUENCY_TIME_LIMIT, true)         \
  _("smallHeapSizeMax", JSGC_SMALL_HEAP_SIZE_MAX, true)                     \
  _("largeHeapSizeMin", JSGC_LARGE_HEAP_SIZE_MIN, true)                     \
  _("highFrequencySmallHeapGrowth", JSGC_HIGH_FREQUENCY_SMALL_HEAP_GROWTH,  \
    true)                                                                   \
  _("highFrequencyLargeHeapGrowth", JSGC_HIGH_FREQUENCY_LARGE_HEAP_GROWTH,  \
    true)                                                                   \
  _("lowFrequencyHeapGrowth", JSGC_LOW_FREQUENCY_HEAP_GROWTH, true)         \
  _("balancedHeapLimitsEnabled", JSGC_BALANCED_HEAP_LIMITS_ENABLED, true)   \
  _("heapGrowthFactor", JSGC_HEAP_GROWTH_FACTOR, true)                      \
  _("allocationThreshold", JSGC_ALLOCATION_THRESHOLD, true)                 \
  _("smallHeapIncrementalLimit", JSGC_SMALL_HEAP_INCREMENTAL_LIMIT, true)   \
  _("largeHeapIncrementalLimit", JSGC_LARGE_HEAP_INCREMENTAL_LIMIT, true)   \
  _("minEmptyChunkCount", JSGC_MIN_EMPTY_CHUNK_COUNT, true)                 \
  _("compactingEnabled", JSGC_COMPACTING_ENABLED, true)                     \
  _("nurseryEnabled", JSGC_NURSERY_ENABLED, true)                           \
  _("parallelMarkingEnabled", JSGC_PARALLEL_MARKING_ENABLED, true)          \
  _("concurrentMarkingEnabled", JSGC_CONCURRENT_MARKING_ENABLED, true)      \
  _("parallelMarkingThresholdMB", JSGC_PARALLEL_MARKING_THRESHOLD_MB, true) \
  _("minLastDitchGCPeriod", JSGC_MIN_LAST_DITCH_GC_PERIOD, true)            \
  _("nurseryEagerCollectionThresholdKB",                                    \
    JSGC_NURSERY_EAGER_COLLECTION_THRESHOLD_KB, true)                       \
  _("nurseryEagerCollectionThresholdPercent",                               \
    JSGC_NURSERY_EAGER_COLLECTION_THRESHOLD_PERCENT, true)                  \
  _("nurseryEagerCollectionTimeoutMS",                                      \
    JSGC_NURSERY_EAGER_COLLECTION_TIMEOUT_MS, true)                         \
  _("nurseryMaxTimeGoalMS", JSGC_NURSERY_MAX_TIME_GOAL_MS, true)            \
  _("zoneAllocDelayKB", JSGC_ZONE_ALLOC_DELAY_KB, true)                     \
  _("mallocThresholdBase", JSGC_MALLOC_THRESHOLD_BASE, true)                \
  _("urgentThreshold", JSGC_URGENT_THRESHOLD_MB, true)                      \
  _("chunkBytes", JSGC_CHUNK_BYTES, false)                                  \
  _("helperThreadRatio", JSGC_HELPER_THREAD_RATIO, true)                    \
  _("maxHelperThreads", JSGC_MAX_HELPER_THREADS, true)                      \
  _("helperThreadCount", JSGC_HELPER_THREAD_COUNT, false)                   \
  _("maxMarkingThreads", JSGC_MAX_MARKING_THREADS, true)                    \
  _("markingThreadCount", JSGC_MARKING_THREAD_COUNT, false)                 \
  _("systemPageSizeKB", JSGC_SYSTEM_PAGE_SIZE_KB, false)                    \
  _("semispaceNurseryEnabled", JSGC_SEMISPACE_NURSERY_ENABLED, true)        \
  _("generateMissingAllocSites", JSGC_GENERATE_MISSING_ALLOC_SITES, true)   \
  _("highFrequencyMode", JSGC_HIGH_FREQUENCY_MODE, false)                   \
  _("storeBufferEntries", JSGC_STORE_BUFFER_ENTRIES, true)                  \
  _("storeBufferScaling", JSGC_STORE_BUFFER_SCALING, true)                  \
  _("incrementalWeakMapMarkingEnabled", JSGC_INCREMENTAL_WEAKMAP_ENABLED, true)

// Get the key and writability give a GC parameter name.
extern bool GetGCParameterInfo(const char* name, JSGCParamKey* keyOut,
                               bool* writableOut);

namespace gc {

void FinishGC(JSContext* cx, JS::GCReason = JS::GCReason::FINISH_GC);

// Abstract base class for exclusive heap access for tracing or GC.
class MOZ_RAII AutoHeapSession {
 public:
  ~AutoHeapSession();
  AutoHeapSession(const AutoHeapSession&) = delete;
  void operator=(const AutoHeapSession&) = delete;

 protected:
  AutoHeapSession(GCRuntime* gc, JS::HeapState state);

 private:
  GCRuntime* gc;
  JS::HeapState prevState;
  mozilla::Maybe<AutoGeckoProfilerEntry> profilingStackFrame;
};

class MOZ_RAII AutoTraceSession : public AutoHeapSession,
                                  public JS::AutoCheckCannotGC {
 public:
  explicit AutoTraceSession(JSRuntime* rt);
};

struct MOZ_RAII AutoFinishGC {
  explicit AutoFinishGC(JSContext* cx, JS::GCReason reason) {
    FinishGC(cx, reason);
  }
};

// This class should be used by any code that needs exclusive access to the heap
// in order to trace through it.
class MOZ_RAII AutoPrepareForTracing : private AutoFinishGC,
                                       public AutoTraceSession {
 public:
  explicit AutoPrepareForTracing(JSContext* cx)
      : AutoFinishGC(cx, JS::GCReason::PREPARE_FOR_TRACING),
        AutoTraceSession(JS_GetRuntime(cx)) {}
};

}  // namespace gc

extern void TraceRuntime(JSTracer* trc);

// Trace roots but don't evict the nursery first; used from DumpHeap.
extern void TraceRuntimeWithoutEviction(JSTracer* trc);

extern void ReleaseAllJITCode(JS::GCContext* gcx);

extern void PrepareForDebugGC(JSRuntime* rt);

/* Functions for managing cross compartment gray pointers. */

extern void NotifyGCNukeWrapper(JSContext* cx, JSObject* wrapper);

extern unsigned NotifyGCPreSwap(JSObject* a, JSObject* b);

extern void NotifyGCPostSwap(JSObject* a, JSObject* b, unsigned removedFlags);

using IterateChunkCallback = void (*)(JSRuntime*, void*, gc::ArenaChunk*,
                                      const JS::AutoRequireNoGC&);
using IterateZoneCallback = void (*)(JSRuntime*, void*, JS::Zone*,
                                     const JS::AutoRequireNoGC&);
using IterateArenaCallback = void (*)(JSRuntime*, void*, gc::Arena*,
                                      JS::TraceKind, size_t,
                                      const JS::AutoRequireNoGC&);
using IterateCellCallback = void (*)(JSRuntime*, void*, JS::GCCellPtr, size_t,
                                     const JS::AutoRequireNoGC&);

/*
 * This function calls |zoneCallback| on every zone, |realmCallback| on
 * every realm, |arenaCallback| on every in-use arena, and |cellCallback|
 * on every in-use cell in the GC heap.
 *
 * Note that no read barrier is triggered on the cells passed to cellCallback,
 * so no these pointers must not escape the callback.
 */
extern void IterateHeapUnbarriered(JSContext* cx, void* data,
                                   IterateZoneCallback zoneCallback,
                                   JS::IterateRealmCallback realmCallback,
                                   IterateArenaCallback arenaCallback,
                                   IterateCellCallback cellCallback,
                                   const js::gc::AutoTraceSession& session);

/*
 * This function is like IterateHeapUnbarriered, but does it for a single zone.
 */
extern void IterateHeapUnbarrieredForZone(
    JSContext* cx, JS::Zone* zone, void* data, IterateZoneCallback zoneCallback,
    JS::IterateRealmCallback realmCallback, IterateArenaCallback arenaCallback,
    IterateCellCallback cellCallback, const js::gc::AutoTraceSession& session);

/*
 * Invoke chunkCallback on every in-use chunk.
 */
extern void IterateChunks(JSContext* cx, void* data,
                          IterateChunkCallback chunkCallback,
                          const js::gc::AutoTraceSession& session);

using IterateScriptCallback = void (*)(JSRuntime*, void*, BaseScript*,
                                       const JS::AutoRequireNoGC&);

/*
 * Invoke scriptCallback on every in-use script for the given realm or for all
 * realms if it is null. The scripts may or may not have bytecode.
 */
extern void IterateScripts(JSContext* cx, JS::Realm* realm, void* data,
                           IterateScriptCallback scriptCallback);

JS::Realm* NewRealm(JSContext* cx, JSPrincipals* principals,
                    const JS::RealmOptions& options);

namespace gc {

void WaitForBackgroundTasks(JSContext* cx);

enum VerifierType { PreBarrierVerifier, PostBarrierVerifier };

#ifdef JS_GC_ZEAL

extern const char ZealModeHelpText[];

/* Check that write barriers have been used correctly. See gc/Verifier.cpp. */
void VerifyBarriers(JSRuntime* rt, VerifierType type);

void MaybeVerifyBarriers(JSContext* cx, bool always = false);

void DumpArenaInfo();

#else

static inline void VerifyBarriers(JSRuntime* rt, VerifierType type) {}

static inline void MaybeVerifyBarriers(JSContext* cx, bool always = false) {}

#endif

/*
 * Instances of this class prevent GC from happening while they are live. If an
 * allocation causes a heap threshold to be exceeded, no GC will be performed
 * and the allocation will succeed. Allocation may still fail for other reasons.
 *
 * Use of this class is highly discouraged, since without GC system memory can
 * become exhausted and this can cause crashes at places where we can't handle
 * allocation failure.
 *
 * Use of this is permissible in situations where it would be impossible (or at
 * least very difficult) to tolerate GC and where only a fixed number of objects
 * are allocated, such as:
 *
 *  - error reporting
 *  - JIT bailout handling
 *  - brain transplants (ProxyObject::swap)
 *  - debugging utilities not exposed to the browser
 *
 * This works by updating the |JSContext::suppressGC| counter which is checked
 * at the start of GC.
 */
class MOZ_RAII JS_HAZ_GC_SUPPRESSED AutoSuppressGC
    : public JS::AutoRequireNoGC {
  int32_t& suppressGC_;

 public:
  explicit AutoSuppressGC(JSContext* cx);

  ~AutoSuppressGC() { suppressGC_--; }
};

const char* StateName(State state);

} /* namespace gc */

/* Use this to avoid assertions when manipulating the wrapper map. */
class MOZ_RAII AutoDisableProxyCheck {
 public:
#ifdef DEBUG
  AutoDisableProxyCheck();
  ~AutoDisableProxyCheck();
#else
  AutoDisableProxyCheck() {}
#endif
};

struct MOZ_RAII AutoDisableCompactingGC {
  explicit AutoDisableCompactingGC(JSContext* cx);
  ~AutoDisableCompactingGC();

 private:
  JSContext* cx;
};

/*
 * Dynamically select the GC heap to allocate into for a graph of GC things.
 *
 * Initially |heap()| will return Heap::Default to select nursery allocation,
 * but when a specified number of nursery collections have been triggered it
 * switches to returning Heap::Tenured.
 */
class MOZ_RAII AutoSelectGCHeap {
 public:
  explicit AutoSelectGCHeap(JSContext* cx,
                            size_t allowedNurseryCollections = 0);
  ~AutoSelectGCHeap();

  gc::Heap heap() const { return heap_; }
  operator gc::Heap() const { return heap_; }

  void onNurseryCollectionEnd();

 private:
  static void NurseryCollectionCallback(JSContext* cx,
                                        JS::GCNurseryProgress progress,
                                        JS::GCReason reason, void* data);

  JSContext* cx_;
  size_t allowedNurseryCollections_;
  gc::Heap heap_ = gc::Heap::Default;
};

} /* namespace js */

#endif /* gc_GC_h */
