/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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 jit_WarpSnapshot_h
#define jit_WarpSnapshot_h

#include "mozilla/LinkedList.h"
#include "mozilla/Variant.h"

#include "builtin/ModuleObject.h"
#include "jit/JitAllocPolicy.h"
#include "jit/JitContext.h"
#include "jit/JitZone.h"
#include "jit/OffthreadSnapshot.h"
#include "jit/TypeData.h"
#include "vm/EnvironmentObject.h"
#include "vm/FunctionFlags.h"  // js::FunctionFlags

namespace js {

class ArgumentsObject;
class CallObject;
class GlobalLexicalEnvironmentObject;
class LexicalEnvironmentObject;
class ModuleEnvironmentObject;
class NamedLambdaObject;

namespace jit {

class CacheIRStubInfo;
class CompileInfo;
class WarpScriptSnapshot;

#define WARP_OP_SNAPSHOT_LIST(_) \
  _(WarpArguments)               \
  _(WarpRegExp)                  \
  _(WarpBuiltinObject)           \
  _(WarpGetIntrinsic)            \
  _(WarpGetImport)               \
  _(WarpRest)                    \
  _(WarpBindUnqualifiedGName)    \
  _(WarpVarEnvironment)          \
  _(WarpLexicalEnvironment)      \
  _(WarpClassBodyEnvironment)    \
  _(WarpBailout)                 \
  _(WarpCacheIR)                 \
  _(WarpInlinedCall)             \
  _(WarpPolymorphicTypes)

// WarpOpSnapshot is the base class for data attached to a single bytecode op by
// WarpOracle. This is typically data that WarpBuilder can't read off-thread
// without racing.
class WarpOpSnapshot : public TempObject,
                       public mozilla::LinkedListElement<WarpOpSnapshot> {
 public:
  enum class Kind : uint16_t {
#define DEF_KIND(KIND) KIND,
    WARP_OP_SNAPSHOT_LIST(DEF_KIND)
#undef DEF_KIND
  };

 private:
  // Bytecode offset.
  uint32_t offset_ = 0;

  Kind kind_;

 protected:
  WarpOpSnapshot(Kind kind, uint32_t offset) : offset_(offset), kind_(kind) {}

 public:
  uint32_t offset() const { return offset_; }
  Kind kind() const { return kind_; }

  template <typename T>
  const T* as() const {
    MOZ_ASSERT(kind_ == T::ThisKind);
    return static_cast<const T*>(this);
  }

  template <typename T>
  T* as() {
    MOZ_ASSERT(kind_ == T::ThisKind);
    return static_cast<T*>(this);
  }

  void trace(JSTracer* trc);

#ifdef JS_JITSPEW
  void dump(GenericPrinter& out, JSScript* script) const;
#endif
};

using WarpOpSnapshotList = mozilla::LinkedList<WarpOpSnapshot>;

// Template object for JSOp::Arguments.
class WarpArguments : public WarpOpSnapshot {
  // Note: this can be nullptr if the realm has no template object yet.
  OffthreadGCPtr<ArgumentsObject*> templateObj_;

 public:
  static constexpr Kind ThisKind = Kind::WarpArguments;

  WarpArguments(uint32_t offset, ArgumentsObject* templateObj)
      : WarpOpSnapshot(ThisKind, offset), templateObj_(templateObj) {}
  ArgumentsObject* templateObj() const { return templateObj_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// The "has RegExpShared" state for JSOp::RegExp's template object.
class WarpRegExp : public WarpOpSnapshot {
  bool hasShared_;

 public:
  static constexpr Kind ThisKind = Kind::WarpRegExp;

  WarpRegExp(uint32_t offset, bool hasShared)
      : WarpOpSnapshot(ThisKind, offset), hasShared_(hasShared) {}
  bool hasShared() const { return hasShared_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// The object for JSOp::BuiltinObject if it exists at compile-time.
class WarpBuiltinObject : public WarpOpSnapshot {
  OffthreadGCPtr<JSObject*> builtin_;

 public:
  static constexpr Kind ThisKind = Kind::WarpBuiltinObject;

  WarpBuiltinObject(uint32_t offset, JSObject* builtin)
      : WarpOpSnapshot(ThisKind, offset), builtin_(builtin) {
    MOZ_ASSERT(builtin);
  }
  JSObject* builtin() const { return builtin_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// The intrinsic for JSOp::GetIntrinsic if it exists at compile-time.
class WarpGetIntrinsic : public WarpOpSnapshot {
  OffthreadGCPtr<Value> intrinsic_;

 public:
  static constexpr Kind ThisKind = Kind::WarpGetIntrinsic;

  WarpGetIntrinsic(uint32_t offset, const Value& intrinsic)
      : WarpOpSnapshot(ThisKind, offset), intrinsic_(intrinsic) {}
  Value intrinsic() const { return intrinsic_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Target module environment and slot information for JSOp::GetImport.
class WarpGetImport : public WarpOpSnapshot {
  OffthreadGCPtr<ModuleEnvironmentObject*> targetEnv_;
  uint32_t numFixedSlots_;
  uint32_t slot_;
  bool needsLexicalCheck_;

 public:
  static constexpr Kind ThisKind = Kind::WarpGetImport;

  WarpGetImport(uint32_t offset, ModuleEnvironmentObject* targetEnv,
                uint32_t numFixedSlots, uint32_t slot, bool needsLexicalCheck)
      : WarpOpSnapshot(ThisKind, offset),
        targetEnv_(targetEnv),
        numFixedSlots_(numFixedSlots),
        slot_(slot),
        needsLexicalCheck_(needsLexicalCheck) {}
  ModuleEnvironmentObject* targetEnv() const { return targetEnv_; }
  uint32_t numFixedSlots() const { return numFixedSlots_; }
  uint32_t slot() const { return slot_; }
  bool needsLexicalCheck() const { return needsLexicalCheck_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Informs WarpBuilder that an IC site is cold and execution should bail out.
class WarpBailout : public WarpOpSnapshot {
 public:
  static constexpr Kind ThisKind = Kind::WarpBailout;

  explicit WarpBailout(uint32_t offset) : WarpOpSnapshot(ThisKind, offset) {}

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Information from a Baseline IC stub.
class WarpCacheIR : public WarpOpSnapshot {
  // Baseline stub code. Stored here to keep the CacheIRStubInfo alive.
  OffthreadGCPtr<JitCode*> stubCode_;
  const CacheIRStubInfo* stubInfo_;

  // Copied Baseline stub data. Allocated in the same LifoAlloc.
  const uint8_t* stubData_;

 public:
  static constexpr Kind ThisKind = Kind::WarpCacheIR;

  WarpCacheIR(uint32_t offset, JitCode* stubCode,
              const CacheIRStubInfo* stubInfo, const uint8_t* stubData)
      : WarpOpSnapshot(ThisKind, offset),
        stubCode_(stubCode),
        stubInfo_(stubInfo),
        stubData_(stubData) {}

  const CacheIRStubInfo* stubInfo() const { return stubInfo_; }
  const uint8_t* stubData() const { return stubData_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// [SMDOC] Warp Nursery Object support
//
// CacheIR stub data can contain nursery allocated objects. This can happen for
// example for GuardSpecificObject/GuardSpecificFunction or GuardProto.
//
// To support nursery GCs in parallel with off-thread compilation, we use the
// following mechanism:
//
// * When WarpOracle copies stub data, it builds a Vector of nursery objects.
//   The nursery object pointers in the stub data are replaced with the
//   corresponding index into this Vector.
//   See WarpScriptOracle::replaceNurseryPointers.
//
// * The Vector is copied to the snapshot and, at the end of compilation, to
//   the IonScript. The Vector is only accessed on the main thread.
//
// * The MIR backend never accesses the raw JSObject*. Instead, it uses
//   MNurseryObject which will load the object at runtime from the IonScript.
//
// WarpObjectField is a helper class to encode/decode a stub data field that
// either stores an object or a nursery index.
class WarpObjectField {
  // This is a nursery index if the low bit is set. Else it's a JSObject*.
  static constexpr uintptr_t NurseryIndexTag = 0x1;
  static constexpr uintptr_t NurseryIndexShift = 1;

  uintptr_t data_;

  explicit WarpObjectField(uintptr_t data) : data_(data) {}

 public:
  static WarpObjectField fromData(uintptr_t data) {
    return WarpObjectField(data);
  }
  static WarpObjectField fromObject(JSObject* obj) {
    return WarpObjectField(uintptr_t(obj));
  }
  static WarpObjectField fromNurseryIndex(uint32_t index) {
    uintptr_t data = (uintptr_t(index) << NurseryIndexShift) | NurseryIndexTag;
    return WarpObjectField(data);
  }

  uintptr_t rawData() const { return data_; }

  bool isNurseryIndex() const { return (data_ & NurseryIndexTag) != 0; }

  uint32_t toNurseryIndex() const {
    MOZ_ASSERT(isNurseryIndex());
    return data_ >> NurseryIndexShift;
  }

  JSObject* toObject() const {
    MOZ_ASSERT(!isNurseryIndex());
    return reinterpret_cast<JSObject*>(data_);
  }
};

// Information for inlining a scripted call IC.
class WarpInlinedCall : public WarpOpSnapshot {
  // Used for generating the correct guards.
  WarpCacheIR* cacheIRSnapshot_;

  // Used for generating the inlined code.
  WarpScriptSnapshot* scriptSnapshot_;
  CompileInfo* info_;

 public:
  static constexpr Kind ThisKind = Kind::WarpInlinedCall;

  WarpInlinedCall(uint32_t offset, WarpCacheIR* cacheIRSnapshot,
                  WarpScriptSnapshot* scriptSnapshot, CompileInfo* info)
      : WarpOpSnapshot(ThisKind, offset),
        cacheIRSnapshot_(cacheIRSnapshot),
        scriptSnapshot_(scriptSnapshot),
        info_(info) {}

  WarpCacheIR* cacheIRSnapshot() const { return cacheIRSnapshot_; }
  WarpScriptSnapshot* scriptSnapshot() const { return scriptSnapshot_; }
  CompileInfo* info() const { return info_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Information for inlining an ordered set of types
class WarpPolymorphicTypes : public WarpOpSnapshot {
  TypeDataList list_;

 public:
  static constexpr Kind ThisKind = Kind::WarpPolymorphicTypes;

  WarpPolymorphicTypes(uint32_t offset, TypeDataList list)
      : WarpOpSnapshot(ThisKind, offset), list_(list) {}

  const TypeDataList& list() const { return list_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Shape for JSOp::Rest.
class WarpRest : public WarpOpSnapshot {
  OffthreadGCPtr<Shape*> shape_;

 public:
  static constexpr Kind ThisKind = Kind::WarpRest;

  WarpRest(uint32_t offset, Shape* shape)
      : WarpOpSnapshot(ThisKind, offset), shape_(shape) {}

  Shape* shape() const { return shape_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Global environment for BindUnqualifiedGName
class WarpBindUnqualifiedGName : public WarpOpSnapshot {
  OffthreadGCPtr<JSObject*> globalEnv_;

 public:
  static constexpr Kind ThisKind = Kind::WarpBindUnqualifiedGName;

  WarpBindUnqualifiedGName(uint32_t offset, JSObject* globalEnv)
      : WarpOpSnapshot(ThisKind, offset), globalEnv_(globalEnv) {}

  JSObject* globalEnv() const { return globalEnv_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Block environment for PushVarEnv
class WarpVarEnvironment : public WarpOpSnapshot {
  OffthreadGCPtr<VarEnvironmentObject*> templateObj_;

 public:
  static constexpr Kind ThisKind = Kind::WarpVarEnvironment;

  WarpVarEnvironment(uint32_t offset, VarEnvironmentObject* templateObj)
      : WarpOpSnapshot(ThisKind, offset), templateObj_(templateObj) {}

  VarEnvironmentObject* templateObj() const { return templateObj_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Block environment for PushLexicalEnv, FreshenLexicalEnv, RecreateLexicalEnv
class WarpLexicalEnvironment : public WarpOpSnapshot {
  OffthreadGCPtr<BlockLexicalEnvironmentObject*> templateObj_;

 public:
  static constexpr Kind ThisKind = Kind::WarpLexicalEnvironment;

  WarpLexicalEnvironment(uint32_t offset,
                         BlockLexicalEnvironmentObject* templateObj)
      : WarpOpSnapshot(ThisKind, offset), templateObj_(templateObj) {}

  BlockLexicalEnvironmentObject* templateObj() const { return templateObj_; }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

// Class body lexical environment for PushClassBodyEnv
class WarpClassBodyEnvironment : public WarpOpSnapshot {
  OffthreadGCPtr<ClassBodyLexicalEnvironmentObject*> templateObj_;

 public:
  static constexpr Kind ThisKind = Kind::WarpClassBodyEnvironment;

  WarpClassBodyEnvironment(uint32_t offset,
                           ClassBodyLexicalEnvironmentObject* templateObj)
      : WarpOpSnapshot(ThisKind, offset), templateObj_(templateObj) {}

  ClassBodyLexicalEnvironmentObject* templateObj() const {
    return templateObj_;
  }

  void traceData(JSTracer* trc);

#ifdef JS_JITSPEW
  void dumpData(GenericPrinter& out) const;
#endif
};

struct NoEnvironment {};
using ConstantObjectEnvironment = OffthreadGCPtr<JSObject*>;
struct FunctionEnvironment {
  OffthreadGCPtr<CallObject*> callObjectTemplate;
  OffthreadGCPtr<NamedLambdaObject*> namedLambdaTemplate;
  gc::Heap initialHeap;

 public:
  FunctionEnvironment(CallObject* callObjectTemplate,
                      NamedLambdaObject* namedLambdaTemplate,
                      gc::Heap initialHeap)
      : callObjectTemplate(callObjectTemplate),
        namedLambdaTemplate(namedLambdaTemplate),
        initialHeap(initialHeap) {}
};

// Snapshot data for the environment object(s) created in the script's prologue.
//
// One of:
//
// * NoEnvironment: No environment object should be set. Leave the slot
//   initialized to |undefined|.
//
// * ConstantObjectEnvironment: Use this JSObject* as environment object.
//
// * FunctionEnvironment: Use the callee's environment chain. Optionally
//   allocate a new NamedLambdaObject and/or CallObject based on
//   namedLambdaTemplate and callObjectTemplate.
using WarpEnvironment =
    mozilla::Variant<NoEnvironment, ConstantObjectEnvironment,
                     FunctionEnvironment>;

// Snapshot data for a single JSScript.
class WarpScriptSnapshot
    : public TempObject,
      public mozilla::LinkedListElement<WarpScriptSnapshot> {
  OffthreadGCPtr<JSScript*> script_;
  WarpEnvironment environment_;
  WarpOpSnapshotList opSnapshots_;

  // If the script has a JSOp::ImportMeta op, this is the module to bake in.
  OffthreadGCPtr<ModuleObject*> moduleObject_;

  // Whether this script is for an arrow function.
  bool isArrowFunction_;

 public:
  WarpScriptSnapshot(JSScript* script, const WarpEnvironment& env,
                     WarpOpSnapshotList&& opSnapshots,
                     ModuleObject* moduleObject);

  JSScript* script() const { return script_; }
  const WarpEnvironment& environment() const { return environment_; }
  const WarpOpSnapshotList& opSnapshots() const { return opSnapshots_; }
  ModuleObject* moduleObject() const { return moduleObject_; }

  bool isArrowFunction() const { return isArrowFunction_; }

  void trace(JSTracer* trc);

#ifdef JS_JITSPEW
  void dump(GenericPrinter& out) const;
#endif
};

// Captures information from previous bailouts to prevent bailout/recompile
// loops.
class WarpBailoutInfo {
  // True if any script in the compilation has the failedBoundsCheck flag. In
  // this case mark bounds checks as non-movable to prevent hoisting them in
  // TryEliminateBoundsCheck.
  bool failedBoundsCheck_ = false;

  // True if any script in the compilation has the failedLexicalCheck flag. In
  // this case mark lexical checks as non-movable.
  bool failedLexicalCheck_ = false;

 public:
  bool failedBoundsCheck() const { return failedBoundsCheck_; }
  void setFailedBoundsCheck() { failedBoundsCheck_ = true; }

  bool failedLexicalCheck() const { return failedLexicalCheck_; }
  void setFailedLexicalCheck() { failedLexicalCheck_ = true; }
};

using WarpScriptSnapshotList = mozilla::LinkedList<WarpScriptSnapshot>;

using WarpZoneStubsSnapshot = JitZone::Stubs<JitCode*>;

// Data allocated by WarpOracle on the main thread that's used off-thread by
// WarpBuilder to build the MIR graph.
class WarpSnapshot : public TempObject {
  // The scripts being compiled.
  WarpScriptSnapshotList scriptSnapshots_;

  // JitZone stubs this compilation depends on.
  const WarpZoneStubsSnapshot zoneStubs_;

  // The global lexical environment and its thisObject(). We don't inline
  // cross-realm calls so this can be stored once per snapshot.
  OffthreadGCPtr<GlobalLexicalEnvironmentObject*> globalLexicalEnv_;
  OffthreadGCPtr<JSObject*> globalLexicalEnvThis_;

  const WarpBailoutInfo bailoutInfo_;

  // List of (originally) nursery-allocated objects. Must only be accessed on
  // the main thread. See also WarpObjectField.
  using NurseryObjectVector = Vector<JSObject*, 0, JitAllocPolicy>;
  NurseryObjectVector nurseryObjects_;

#ifdef JS_CACHEIR_SPEW
  bool needsFinalWarmUpCount_ = false;
#endif

#ifdef DEBUG
  // A hash of the stub pointers and entry counts for each of the ICs
  // in this snapshot.
  mozilla::HashNumber icHash_ = 0;
#endif

 public:
  explicit WarpSnapshot(JSContext* cx, TempAllocator& alloc,
                        WarpScriptSnapshotList&& scriptSnapshots,
                        const WarpZoneStubsSnapshot& zoneStubs,
                        const WarpBailoutInfo& bailoutInfo,
                        bool recordWarmUpCount);

  WarpScriptSnapshot* rootScript() { return scriptSnapshots_.getFirst(); }
  const WarpScriptSnapshotList& scripts() const { return scriptSnapshots_; }

  JitCode* getZoneStub(JitZone::StubKind kind) const {
    MOZ_ASSERT(zoneStubs_[kind]);
    return zoneStubs_[kind];
  }

  GlobalLexicalEnvironmentObject* globalLexicalEnv() const {
    return globalLexicalEnv_;
  }
  JSObject* globalLexicalEnvThis() const { return globalLexicalEnvThis_; }

  void trace(JSTracer* trc);

  const WarpBailoutInfo& bailoutInfo() const { return bailoutInfo_; }

  NurseryObjectVector& nurseryObjects() { return nurseryObjects_; }
  const NurseryObjectVector& nurseryObjects() const { return nurseryObjects_; }

#ifdef DEBUG
  mozilla::HashNumber icHash() const { return icHash_; }
  void setICHash(mozilla::HashNumber hash) { icHash_ = hash; }
#endif

#ifdef JS_JITSPEW
  void dump() const;
  void dump(GenericPrinter& out) const;
#endif

#ifdef JS_CACHEIR_SPEW
  bool needsFinalWarmUpCount() const { return needsFinalWarmUpCount_; }
#endif
};

}  // namespace jit
}  // namespace js

#endif /* jit_WarpSnapshot_h */
