/* 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_TracingAPI_h
#define js_TracingAPI_h

#include "js/GCTypeMacros.h"
#include "js/HeapAPI.h"
#include "js/TraceKind.h"

class JS_PUBLIC_API JSTracer;

namespace JS {
class JS_PUBLIC_API CallbackTracer;
template <typename T>
class Heap;
template <typename T>
class TenuredHeap;

/** Returns a static string equivalent of |kind|. */
JS_PUBLIC_API const char* GCTraceKindToAscii(JS::TraceKind kind);

/** Returns the base size in bytes of the GC thing of kind |kind|. */
JS_PUBLIC_API size_t GCTraceKindSize(JS::TraceKind kind);

// Kinds of JSTracer.
enum class TracerKind : uint8_t {
  // Generic tracers: Internal tracers that have a different virtual method
  // called for each edge kind.
  Marking,
  Tenuring,
  Moving,
  ClearEdges,
  Sweeping,
  MinorSweeping,
  Barrier,

  // Callback tracers: General-purpose tracers that have a single virtual
  // method called on every edge.
  //
  // Order is important. All callback kinds must follow this one.
  Callback,

  // Specific kinds of callback tracer.
  UnmarkGray,
  VerifyTraceProtoAndIface,
  CompartmentCheck,
  HeapCheck
};

enum class WeakMapTraceAction : uint8_t {
  /**
   * Do not trace into weak map keys or values during traversal. Users must
   * handle weak maps manually.
   */
  Skip,

  /**
   * Do true ephemeron marking with a weak key lookup marking phase. This is
   * the default for GCMarker.
   */
  Expand,

  /**
   * Trace through to all values, irrespective of whether the keys are live
   * or not. Used for non-marking tracers.
   */
  TraceValues,

  /**
   * Trace through to all keys and values, irrespective of whether the keys
   * are live or not. Used for non-marking tracers.
   */
  TraceKeysAndValues
};

// Whether a tracer should trace weak edges. GCMarker sets this to Skip.
enum class WeakEdgeTraceAction : bool { Skip, Trace };

struct TraceOptions {
  JS::WeakMapTraceAction weakMapAction = WeakMapTraceAction::TraceValues;
  JS::WeakEdgeTraceAction weakEdgeAction = WeakEdgeTraceAction::Trace;

  TraceOptions() = default;
  TraceOptions(JS::WeakMapTraceAction weakMapActionArg,
               JS::WeakEdgeTraceAction weakEdgeActionArg)
      : weakMapAction(weakMapActionArg), weakEdgeAction(weakEdgeActionArg) {}
  MOZ_IMPLICIT TraceOptions(JS::WeakMapTraceAction weakMapActionArg)
      : weakMapAction(weakMapActionArg) {}
  MOZ_IMPLICIT TraceOptions(JS::WeakEdgeTraceAction weakEdgeActionArg)
      : weakEdgeAction(weakEdgeActionArg) {}
};

class AutoTracingIndex;

// Optional context information that can be used to construct human readable
// descriptions of what is being traced.
class TracingContext {
 public:
  // Access to the tracing context: When tracing with a JS::CallbackTracer, we
  // invoke the callback with the edge location and the type of target. This is
  // useful for operating on the edge in the abstract or on the target thing,
  // satisfying most common use cases.  However, some tracers need additional
  // detail about the specific edge that is being traced in order to be
  // useful. Unfortunately, the raw pointer to the edge that we provide is not
  // enough information to infer much of anything useful about that edge.
  //
  // In order to better support use cases that care in particular about edges --
  // as opposed to the target thing -- tracing implementations are responsible
  // for providing extra context information about each edge they trace, as it
  // is traced. This contains, at a minimum, an edge name and, when tracing an
  // array, the index. Further specialization can be achieved (with some
  // complexity), by associating a functor with the tracer so that, when
  // requested, the user can generate totally custom edge descriptions.

  // Returns the current edge's index, if marked as part of an array of edges.
  // This must be called only inside the trace callback. When not tracing an
  // array, the value will be InvalidIndex.
  constexpr static size_t InvalidIndex = size_t(-1);
  size_t index() const { return index_; }

  // Build a description of this edge in the heap graph. This call may invoke
  // the context functor, if set, which may inspect arbitrary areas of the
  // heap. On the other hand, the description provided by this method may be
  // substantially more accurate and useful than those provided by only the
  // name and index.
  void getEdgeName(const char* name, char* buffer, size_t bufferSize);

  // The trace implementation may associate a callback with one or more edges
  // using AutoTracingDetails. This functor is called by getEdgeName and
  // is responsible for providing a textual representation of the edge currently
  // being traced. The callback has access to the full heap, including the
  // currently set tracing context.
  class Functor {
   public:
    virtual void operator()(TracingContext* tcx, const char* name, char* buf,
                            size_t bufsize) = 0;
  };

 private:
  friend class AutoTracingIndex;
  size_t index_ = InvalidIndex;

  friend class AutoTracingDetails;
  Functor* functor_ = nullptr;
};

}  // namespace JS

class JS_PUBLIC_API JSTracer {
 public:
  // Return the runtime set on the tracer.
  JSRuntime* runtime() const { return runtime_; }

  JS::TracerKind kind() const { return kind_; }
  bool isGenericTracer() const { return kind_ < JS::TracerKind::Callback; }
  bool isCallbackTracer() const { return kind_ >= JS::TracerKind::Callback; }
  bool isMarkingTracer() const { return kind_ == JS::TracerKind::Marking; }
  bool isTenuringTracer() const { return kind_ == JS::TracerKind::Tenuring; }

  inline JS::CallbackTracer* asCallbackTracer();

  JS::WeakMapTraceAction weakMapAction() const {
    return options_.weakMapAction;
  }
  bool traceWeakEdges() const {
    return options_.weakEdgeAction == JS::WeakEdgeTraceAction::Trace;
  }

  JS::TracingContext& context() { return context_; }

  // These methods are called when the tracer encounters an edge. Clients should
  // override them to receive notifications when an edge of each type is
  // visited.
  //
  // The caller updates the edge with the return value (if different).
  //
  // In C++, overriding a method hides all methods in the base class with that
  // name, not just methods with that signature. Thus, the typed edge methods
  // have to have distinct names to allow us to override them individually,
  // which is freqently useful if, for example, we only want to process one type
  // of edge.
#define DEFINE_ON_EDGE_METHOD(name, type, _1, _2) \
  virtual bool on##name##Edge(type** thingp, const char* name) = 0;
  JS_FOR_EACH_TRACEKIND(DEFINE_ON_EDGE_METHOD)
#undef DEFINE_ON_EDGE_METHOD

 protected:
  JSTracer(JSRuntime* rt, JS::TracerKind kind,
           JS::TraceOptions options = JS::TraceOptions())
      : runtime_(rt), kind_(kind), options_(options) {}

 private:
  JSRuntime* const runtime_;
  JS::TracingContext context_;
  const JS::TracerKind kind_;
  const JS::TraceOptions options_;
};

namespace js {

// A CRTP helper class that implements a JSTracer by calling a template method
// on the derived tracer type for each edge kind.
template <typename T>
class GenericTracerImpl : public JSTracer {
 public:
  GenericTracerImpl(JSRuntime* rt, JS::TracerKind kind,
                    JS::TraceOptions options)
      : JSTracer(rt, kind, options) {}

 private:
  T* derived() { return static_cast<T*>(this); }

#define DEFINE_ON_EDGE_METHOD(name, type, _1, _2)              \
  bool on##name##Edge(type** thingp, const char* name) final { \
    return derived()->onEdge(thingp, name);                    \
  }
  JS_FOR_EACH_TRACEKIND(DEFINE_ON_EDGE_METHOD)
#undef DEFINE_ON_EDGE_METHOD
};

}  // namespace js

namespace JS {

class JS_PUBLIC_API CallbackTracer
    : public js::GenericTracerImpl<CallbackTracer> {
 public:
  CallbackTracer(JSRuntime* rt, JS::TracerKind kind = JS::TracerKind::Callback,
                 JS::TraceOptions options = JS::TraceOptions())
      : GenericTracerImpl(rt, kind, options) {
    MOZ_ASSERT(isCallbackTracer());
  }
  CallbackTracer(JSContext* cx, JS::TracerKind kind = JS::TracerKind::Callback,
                 JS::TraceOptions options = JS::TraceOptions());

  // Override this method to receive notification when a node in the GC
  // heap graph is visited.
  virtual bool onChild(JS::GCCellPtr thing, const char* name) = 0;

 private:
  template <typename T>
  bool onEdge(T** thingp, const char* name) {
    T* thing = *thingp;
    if (!thing) {
      return true;
    }
    return onChild(JS::GCCellPtr(thing), name);
  }
  friend class js::GenericTracerImpl<CallbackTracer>;
};

// Set the index portion of the tracer's context for the current range.
class MOZ_RAII AutoTracingIndex {
  JSTracer* trc_;

 public:
  explicit AutoTracingIndex(JSTracer* trc, size_t initial = 0) : trc_(trc) {
    MOZ_ASSERT(trc_->context().index_ == TracingContext::InvalidIndex);
    trc_->context().index_ = initial;
  }
  ~AutoTracingIndex() {
    MOZ_ASSERT(trc_->context().index_ != TracingContext::InvalidIndex);
    trc_->context().index_ = TracingContext::InvalidIndex;
  }

  void operator++() {
    MOZ_ASSERT(trc_->context().index_ != TracingContext::InvalidIndex);
    ++trc_->context().index_;
  }
};

// Set a context callback for the trace callback to use, if it needs a detailed
// edge description.
class MOZ_RAII AutoTracingDetails {
  JSTracer* trc_;

 public:
  AutoTracingDetails(JSTracer* trc, TracingContext::Functor& func) : trc_(trc) {
    MOZ_ASSERT(trc_->context().functor_ == nullptr);
    trc_->context().functor_ = &func;
  }
  ~AutoTracingDetails() {
    MOZ_ASSERT(trc_->context().functor_);
    trc_->context().functor_ = nullptr;
  }
};

// Save and clear tracing context when performing nested tracing.
class MOZ_RAII AutoClearTracingContext {
  JSTracer* trc_;
  TracingContext prev_;

 public:
  explicit AutoClearTracingContext(JSTracer* trc)
      : trc_(trc), prev_(trc->context()) {
    trc_->context() = TracingContext();
  }

  ~AutoClearTracingContext() { trc_->context() = prev_; }
};

}  // namespace JS

JS::CallbackTracer* JSTracer::asCallbackTracer() {
  MOZ_ASSERT(isCallbackTracer());
  return static_cast<JS::CallbackTracer*>(this);
}

namespace js {

class AbstractGeneratorObject;
class SavedFrame;
namespace wasm {
class AnyRef;
}  // namespace wasm

namespace gc {

#define JS_DECLARE_TRACE_EXTERNAL_EDGE(type)                               \
  extern JS_PUBLIC_API void TraceExternalEdge(JSTracer* trc, type* thingp, \
                                              const char* name);

// Declare edge-tracing function overloads for public GC pointer types.
JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(JS_DECLARE_TRACE_EXTERNAL_EDGE)
JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(JS_DECLARE_TRACE_EXTERNAL_EDGE)

#undef JS_DECLARE_TRACE_EXTERNAL_EDGE

}  // namespace gc
}  // namespace js

namespace JS {

// The JS::TraceEdge family of functions traces the given GC thing reference.
// This performs the tracing action configured on the given JSTracer: typically
// calling the JSTracer::callback or marking the thing as live.
//
// The argument to JS::TraceEdge is an in-out param: when the function returns,
// the garbage collector might have moved the GC thing. In this case, the
// reference passed to JS::TraceEdge will be updated to the thing's new
// location. Callers of this method are responsible for updating any state that
// is dependent on the object's address. For example, if the object's address
// is used as a key in a hashtable, then the object must be removed and
// re-inserted with the correct hash.
//
// Note that while |edgep| must never be null, it is fine for |*edgep| to be
// nullptr.

template <typename T>
inline void TraceEdge(JSTracer* trc, JS::Heap<T>* thingp, const char* name) {
  MOZ_ASSERT(thingp);
  if (*thingp) {
    js::gc::TraceExternalEdge(trc, thingp->unsafeAddress(), name);
  }
}

template <typename T>
inline void TraceEdge(JSTracer* trc, JS::TenuredHeap<T>* thingp,
                      const char* name) {
  MOZ_ASSERT(thingp);
  if (T ptr = thingp->unbarrieredGetPtr()) {
    js::gc::TraceExternalEdge(trc, &ptr, name);
    thingp->unbarrieredSetPtr(ptr);
  }
}

// Edges that are always traced as part of root marking do not require
// incremental barriers. |JS::TraceRoot| overloads allow for marking
// non-barriered pointers but assert that this happens during root marking.
//
// Note that while |edgep| must never be null, it is fine for |*edgep| to be
// nullptr.
#define JS_DECLARE_TRACE_ROOT(type)                               \
  extern JS_PUBLIC_API void TraceRoot(JSTracer* trc, type* edgep, \
                                      const char* name);

// Declare edge-tracing function overloads for public GC pointer types.
JS_FOR_EACH_PUBLIC_GC_POINTER_TYPE(JS_DECLARE_TRACE_ROOT)
JS_FOR_EACH_PUBLIC_TAGGED_GC_POINTER_TYPE(JS_DECLARE_TRACE_ROOT)

// We also require overloads for these purely-internal types.  These overloads
// ought not be in public headers, and they should use a different name in order
// to not be *actual* overloads, but for the moment we still declare them here.
JS_DECLARE_TRACE_ROOT(js::AbstractGeneratorObject*)
JS_DECLARE_TRACE_ROOT(js::SavedFrame*)
JS_DECLARE_TRACE_ROOT(js::wasm::AnyRef)

#undef JS_DECLARE_TRACE_ROOT

extern JS_PUBLIC_API void TraceChildren(JSTracer* trc, GCCellPtr thing);

}  // namespace JS

namespace js {

inline bool IsTracerKind(JSTracer* trc, JS::TracerKind kind) {
  return trc->kind() == kind;
}

// Trace an edge that is not a GC root and is not wrapped in a barriered
// wrapper for some reason.
extern JS_PUBLIC_API void UnsafeTraceManuallyBarrieredEdge(JSTracer* trc,
                                                           JSObject** thingp,
                                                           const char* name);

namespace gc {

// Return true if the given edge is not live and is about to be swept.
template <typename T>
extern JS_PUBLIC_API bool TraceWeakEdge(JSTracer* trc, JS::Heap<T>* thingp);

}  // namespace gc

#ifdef DEBUG
/*
 * Return whether the runtime is currently being destroyed, for use in
 * assertions.
 */
extern JS_PUBLIC_API bool RuntimeIsBeingDestroyed();
#endif

}  // namespace js

#endif /* js_TracingAPI_h */
