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

/*
 * GC-internal definitions.
 */

#ifndef gc_GCInternals_h
#define gc_GCInternals_h

#include "mozilla/Maybe.h"
#include "mozilla/Range.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/Vector.h"

#include "gc/Cell.h"
#include "gc/GC.h"
#include "gc/GCContext.h"
#include "gc/GCMarker.h"
#include "vm/GeckoProfiler.h"
#include "vm/HelperThreads.h"
#include "vm/JSContext.h"

namespace js {

class GCMarker;

namespace gc {

/*
 * There are a couple of classes here that serve mostly as "tokens" indicating
 * that a precondition holds. Some functions force the caller to possess such a
 * token because they require the precondition to hold, and it is better to make
 * the precondition explicit at the API entry point than to crash in an
 * assertion later on when it is relied upon.
 */

struct MOZ_RAII AutoAssertNoNurseryAlloc {
#ifdef DEBUG
  AutoAssertNoNurseryAlloc();
  ~AutoAssertNoNurseryAlloc();
#else
  AutoAssertNoNurseryAlloc() {}
#endif
};

/*
 * A class that serves as a token that the nursery in the current thread's zone
 * group is empty.
 */
class MOZ_RAII AutoAssertEmptyNursery {
 protected:
  JSContext* cx;

  mozilla::Maybe<AutoAssertNoNurseryAlloc> noAlloc;

  // Check that the nursery is empty.
  void checkCondition(JSContext* cx);

  // For subclasses that need to empty the nursery in their constructors.
  AutoAssertEmptyNursery() : cx(nullptr) {}

 public:
  explicit AutoAssertEmptyNursery(JSContext* cx) : cx(nullptr) {
    checkCondition(cx);
  }

  AutoAssertEmptyNursery(const AutoAssertEmptyNursery& other)
      : AutoAssertEmptyNursery(other.cx) {}
};

/*
 * Evict the nursery upon construction. Serves as a token indicating that the
 * nursery is empty. (See AutoAssertEmptyNursery, above.)
 */
class MOZ_RAII AutoEmptyNursery : public AutoAssertEmptyNursery {
 public:
  explicit AutoEmptyNursery(JSContext* cx);
};

class MOZ_RAII AutoGCSession : public AutoHeapSession {
 public:
  explicit AutoGCSession(GCRuntime* gc, JS::HeapState state)
      : AutoHeapSession(gc, state) {}
};

class MOZ_RAII AutoMajorGCProfilerEntry : public AutoGeckoProfilerEntry {
 public:
  explicit AutoMajorGCProfilerEntry(GCRuntime* gc);
};

// This class should be used by any code that needs exclusive access to the heap
// in order to trace through it.
//
// This version also empties the nursery after finishing any ongoing GC.
class MOZ_RAII AutoEmptyNurseryAndPrepareForTracing : private AutoFinishGC,
                                                      public AutoEmptyNursery,
                                                      public AutoTraceSession {
 public:
  explicit AutoEmptyNurseryAndPrepareForTracing(JSContext* cx)
      : AutoFinishGC(cx, JS::GCReason::PREPARE_FOR_TRACING),
        AutoEmptyNursery(cx),
        AutoTraceSession(cx->runtime()) {}
};

// Set compartments' maybeAlive flags if anything is marked while this class is
// live. This is used while marking roots.
class AutoUpdateLiveCompartments {
  GCRuntime* gc;

 public:
  explicit AutoUpdateLiveCompartments(GCRuntime* gc);
  ~AutoUpdateLiveCompartments();
};

class MOZ_RAII AutoRunParallelTask : public GCParallelTask {
  // This class takes a pointer to a member function of GCRuntime.
  using TaskFunc = JS_MEMBER_FN_PTR_TYPE(GCRuntime, void);

  TaskFunc func_;
  AutoLockHelperThreadState& lock_;

 public:
  AutoRunParallelTask(GCRuntime* gc, TaskFunc func, gcstats::PhaseKind phase,
                      GCUse use, AutoLockHelperThreadState& lock)
      : GCParallelTask(gc, phase, use), func_(func), lock_(lock) {
    gc->startTask(*this, lock_);
  }

  ~AutoRunParallelTask() { gc->joinTask(*this, lock_); }

  void run(AutoLockHelperThreadState& lock) override {
    AutoUnlockHelperThreadState unlock(lock);

    // The hazard analysis can't tell what the call to func_ will do but it's
    // not allowed to GC.
    JS::AutoSuppressGCAnalysis nogc;

    // Call pointer to member function on |gc|.
    JS_CALL_MEMBER_FN_PTR(gc, func_);
  }
};

#ifdef JS_GC_ZEAL

class MOZ_RAII AutoStopVerifyingBarriers {
  GCRuntime* gc;
  bool restartPreVerifier;

 public:
  AutoStopVerifyingBarriers(JSRuntime* rt, bool isShutdown) : gc(&rt->gc) {
    if (gc->isVerifyPreBarriersEnabled()) {
      gc->endVerifyPreBarriers();
      restartPreVerifier = !isShutdown;
    } else {
      restartPreVerifier = false;
    }
  }

  ~AutoStopVerifyingBarriers() {
    // Nasty special case: verification runs a minor GC, which *may* nest
    // inside of an outer minor GC. This is not allowed by the
    // gc::Statistics phase tree. So we pause the "real" GC, if in fact one
    // is in progress.
    gcstats::PhaseKind outer = gc->stats().currentPhaseKind();
    if (outer != gcstats::PhaseKind::NONE) {
      gc->stats().endPhase(outer);
    }
    MOZ_ASSERT(gc->stats().currentPhaseKind() == gcstats::PhaseKind::NONE);

    if (restartPreVerifier) {
      gc->startVerifyPreBarriers();
    }

    if (outer != gcstats::PhaseKind::NONE) {
      gc->stats().beginPhase(outer);
    }
  }
};
#else
struct MOZ_RAII AutoStopVerifyingBarriers {
  AutoStopVerifyingBarriers(JSRuntime*, bool) {}
};
#endif /* JS_GC_ZEAL */

class MOZ_RAII AutoPoisonFreedJitCode {
  JS::GCContext* const gcx;

 public:
  explicit AutoPoisonFreedJitCode(JS::GCContext* gcx) : gcx(gcx) {}
  ~AutoPoisonFreedJitCode() { gcx->poisonJitCode(); }
};

// Set/restore the GCContext GC use flag for the current thread.

class MOZ_RAII AutoSetThreadGCUse {
 public:
  AutoSetThreadGCUse(JS::GCContext* gcx, GCUse use)
      : gcx(gcx), prevUse(gcx->gcUse_) {
    gcx->gcUse_ = use;
  }
  explicit AutoSetThreadGCUse(GCUse use)
      : AutoSetThreadGCUse(TlsGCContext.get(), use) {}

  ~AutoSetThreadGCUse() { gcx->gcUse_ = prevUse; }

 protected:
  JS::GCContext* gcx;
  GCUse prevUse;
};

template <GCUse Use>
class AutoSetThreadGCUseT : public AutoSetThreadGCUse {
 public:
  explicit AutoSetThreadGCUseT(JS::GCContext* gcx)
      : AutoSetThreadGCUse(gcx, Use) {}
  AutoSetThreadGCUseT() : AutoSetThreadGCUseT(TlsGCContext.get()) {}
};

using AutoSetThreadIsPerformingGC = AutoSetThreadGCUseT<GCUse::Unspecified>;
using AutoSetThreadIsMarking = AutoSetThreadGCUseT<GCUse::Marking>;
using AutoSetThreadIsFinalizing = AutoSetThreadGCUseT<GCUse::Finalizing>;

class AutoSetThreadIsSweeping : public AutoSetThreadGCUseT<GCUse::Sweeping> {
 public:
  explicit AutoSetThreadIsSweeping(JS::GCContext* gcx,
                                   JS::Zone* sweepZone = nullptr)
      : AutoSetThreadGCUseT(gcx) {
#ifdef DEBUG
    prevZone = gcx->gcSweepZone_;
    gcx->gcSweepZone_ = sweepZone;
#endif
  }
  explicit AutoSetThreadIsSweeping(JS::Zone* sweepZone = nullptr)
      : AutoSetThreadIsSweeping(TlsGCContext.get(), sweepZone) {}

  ~AutoSetThreadIsSweeping() {
#ifdef DEBUG
    MOZ_ASSERT_IF(prevUse == GCUse::None, !prevZone);
    gcx->gcSweepZone_ = prevZone;
#endif
  }

 private:
#ifdef DEBUG
  JS::Zone* prevZone;
#endif
};

class MOZ_RAII AutoDisallowPreWriteBarrier {
 public:
  explicit AutoDisallowPreWriteBarrier(JS::GCContext* gcx) {
#ifdef DEBUG
    gcx_ = gcx;
    MOZ_ASSERT(gcx->preWriteBarrierAllowed_);
    gcx->preWriteBarrierAllowed_ = false;
#endif
  }
  ~AutoDisallowPreWriteBarrier() {
#ifdef DEBUG
    MOZ_ASSERT(!gcx_->preWriteBarrierAllowed_);
    gcx_->preWriteBarrierAllowed_ = true;
#endif
  }

 private:
#ifdef DEBUG
  JS::GCContext* gcx_;
#endif
};

#ifdef JSGC_HASH_TABLE_CHECKS
void CheckHashTablesAfterMovingGC(JSRuntime* rt);
void CheckHeapAfterGC(JSRuntime* rt);
#endif

struct MovingTracer final : public GenericTracerImpl<MovingTracer> {
  explicit MovingTracer(JSRuntime* rt);

 private:
  template <typename T>
  bool onEdge(T** thingp, const char* name);
  friend class GenericTracerImpl<MovingTracer>;
};

struct MinorSweepingTracer final
    : public GenericTracerImpl<MinorSweepingTracer> {
  explicit MinorSweepingTracer(JSRuntime* rt);

 private:
  template <typename T>
  bool onEdge(T** thingp, const char* name);
  friend class GenericTracerImpl<MinorSweepingTracer>;
};

class MOZ_RAII AutoUpdateMarkStackRanges {
  GCMarker& marker_;

 public:
  explicit AutoUpdateMarkStackRanges(GCMarker& marker) : marker_(marker) {
    marker_.updateRangesAtStartOfSlice();
  }
  ~AutoUpdateMarkStackRanges() { marker_.updateRangesAtEndOfSlice(); }
};

extern void DelayCrossCompartmentGrayMarking(GCMarker* maybeMarker,
                                             JSObject* src);

inline bool IsOOMReason(JS::GCReason reason) {
  return reason == JS::GCReason::LAST_DITCH ||
         reason == JS::GCReason::MEM_PRESSURE;
}

void* AllocateTenuredCellInGC(JS::Zone* zone, AllocKind thingKind);

void ReadProfileEnv(const char* envName, const char* helpText, bool* enableOut,
                    bool* workersOut, mozilla::TimeDuration* thresholdOut);

bool ShouldPrintProfile(JSRuntime* runtime, bool enable, bool workers,
                        mozilla::TimeDuration threshold,
                        mozilla::TimeDuration duration);

using CharRange = mozilla::Range<const char>;
using CharRangeVector = Vector<CharRange, 0, SystemAllocPolicy>;

// Split a string on a given character and produce a vector of CharRanges that
// reference the input string.
extern bool SplitStringBy(const char* string, char delimiter,
                          CharRangeVector* resultOut);
extern bool SplitStringBy(const CharRange& string, char delimiter,
                          CharRangeVector* resultOut);

} /* namespace gc */
} /* namespace js */

#endif /* gc_GCInternals_h */
