/* 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_IonTypes_h
#define jit_IonTypes_h

#include "mozilla/HashFunctions.h"

#include <algorithm>
#include <stdint.h>

#include "jstypes.h"
#include "NamespaceImports.h"

#include "jit/ABIFunctionType.h"

#include "js/ScalarType.h"  // js::Scalar::Type
#include "js/Value.h"

namespace js {

// Each IonScript has a unique compilation id. This is used to sweep/ignore
// constraints for IonScripts that have been invalidated/destroyed.
class IonCompilationId {
  // Use two 32-bit integers instead of uint64_t to avoid 8-byte alignment on
  // some 32-bit platforms.
  uint32_t idLo_;
  uint32_t idHi_;

 public:
  explicit IonCompilationId(uint64_t id)
      : idLo_(id & UINT32_MAX), idHi_(id >> 32) {}
  bool operator==(const IonCompilationId& other) const {
    return idLo_ == other.idLo_ && idHi_ == other.idHi_;
  }
  bool operator!=(const IonCompilationId& other) const {
    return !operator==(other);
  }
};

namespace jit {

using RecoverOffset = uint64_t;
using SnapshotOffset = uint32_t;

// The maximum size of any buffer associated with an assembler or code object.
// This is chosen to not overflow a signed integer, leaving room for an extra
// bit on offsets.
static const uint32_t MAX_BUFFER_SIZE = (1 << 30) - 1;

// Maximum number of scripted arg slots.
static const uint32_t SNAPSHOT_MAX_NARGS = 127;

static const RecoverOffset INVALID_RECOVER_OFFSET = RecoverOffset(-1);
static const SnapshotOffset INVALID_SNAPSHOT_OFFSET = SnapshotOffset(-1);

/*
 * [SMDOC] Avoiding repeated bailouts / invalidations
 *
 * To avoid getting trapped in a "compilation -> bailout -> invalidation ->
 * recompilation -> bailout -> invalidation -> ..." loop, every snapshot in
 * Warp code is assigned a BailoutKind. If we bail out at that snapshot,
 * FinishBailoutToBaseline will examine the BailoutKind and take appropriate
 * action. In general:
 *
 * 1. If the bailing instruction comes from transpiled CacheIR, then when we
 *    bail out and continue execution in the baseline interpreter, the
 *    corresponding stub should fail a guard. As a result, we will either
 *    increment the enteredCount for a subsequent stub or attach a new stub,
 *    either of which will prevent WarpOracle from transpiling the failing stub
 *    when we recompile.
 *
 *    Note: this means that every CacheIR op that can bail out in Warp must
 *    have an equivalent guard in the baseline CacheIR implementation.
 *
 *    FirstExecution works according to the same principles: we have never hit
 *    this IC before, but after we bail to baseline we will attach a stub and
 *    recompile with better CacheIR information.
 *
 * 2. If the bailout occurs because an assumption we made in WarpBuilder was
 *    invalidated, then FinishBailoutToBaseline will set a flag on the script
 *    to avoid that assumption in the future: for example, UninitializedLexical.
 *
 * 3. Similarly, if the bailing instruction is generated or modified by a MIR
 *    optimization, then FinishBailoutToBaseline will set a flag on the script
 *    to make that optimization more conservative in the future.  Examples
 *    include LICM, EagerTruncation, and HoistBoundsCheck.
 *
 * 4. Some bailouts can't be handled in Warp, even after a recompile. For
 *    example, Warp does not support catching exceptions. If this happens
 *    too often, then the cost of bailing out repeatedly outweighs the
 *    benefit of Warp compilation, so we invalidate the script and disable
 *    Warp compilation.
 *
 * 5. Some bailouts don't happen in performance-sensitive code: for example,
 *    the |debugger| statement. We just ignore those.
 */
enum class BailoutKind : uint8_t {
  Unknown,

  // An instruction generated by the transpiler. If this instruction bails out,
  // attaching a new stub in baseline will invalidate the current Warp script
  // and avoid a bailout loop.
  TranspiledCacheIR,

  // A GuardMultipleShapes instruction generated by the transpiler after stub
  // folding. If this instruction bails out, we will return to baseline and see
  // if we add a new case to the folded stub. If we do, this should not count as
  // a bailout for the purpose of eventually invalidating this script.
  //
  // If the script containing the folded stub was inlined monomorphically,
  // there's no direct connection between the inner script and the outer script.
  // We store the inner and outer scripts so that we know which outer script to
  // notify if we successfully add a new case to the folded stub.
  StubFoldingGuardMultipleShapes,

  // An optimistic unbox on the cold path for a non-Value phi failed. If this
  // instruction bails out, we will invalidate the script and mark the
  // HadSpeculativePhiBailout flag on the script.
  SpeculativePhi,

  // A conversion inserted by a type policy. If this instruction bails out,
  // we expect to throw an error. If this happens too frequently, we will
  // invalidate the current Warp script and disable recompilation.
  TypePolicy,

  // An instruction hoisted by LICM.  If this instruction bails out, we will
  // bail out to baseline to see if we attach a new stub. If we do, then the
  // more than once, we will invalidate the current Warp script and
  // mark the hadLICMInvalidation flag on the script.
  LICM,

  // An instruction moved up by InstructionReordering.  If this
  // instruction bails out, we will mark the ReorderingBailout flag on
  // the script. If this happens too frequently, we will invalidate
  // the script.
  InstructionReordering,

  // An instruction created or hoisted by tryHoistBoundsCheck.
  // If this instruction bails out, we will invalidate the current Warp script
  // and mark the HoistBoundsCheckBailout flag on the script.
  HoistBoundsCheck,

  // An eager truncation generated by range analysis.
  // If this instruction bails out, we will invalidate the current Warp script
  // and mark the EagerTruncationBailout flag on the script.
  EagerTruncation,

  // A folded unbox instruction generated by FoldLoadsWithUnbox.
  // If this instruction bails out, we will invalidate the current Warp script
  // and mark the UnboxFoldingBailout flag on the script.
  UnboxFolding,

  // An inevitable bailout (MBail instruction or type barrier that always bails)
  Inevitable,

  // Bailing out during a VM call. Many possible causes that are hard
  // to distinguish statically at snapshot construction time.
  // We just lump them together.
  DuringVMCall,

  // A spread call or funapply had more than JIT_ARGS_LENGTH_MAX arguments.
  // We bail out to handle this in the VM. If this happens too frequently,
  // we will invalidate the current Warp script and disable recompilation.
  TooManyArguments,

  // We hit an active |debugger;| statement.
  Debugger,

  // We hit this code for the first time.
  FirstExecution,

  // A lexical check failed. We will set lexical checks as unmovable.
  UninitializedLexical,

  // A bailout to baseline from Ion on exception to handle Debugger hooks.
  IonExceptionDebugMode,

  // A bailout to baseline from Ion on exception to handle a finally block.
  Finally,

  // We returned to a stack frame after invalidating its IonScript.
  OnStackInvalidation,

  // We returned to a stack frame while calling the |return| method of an
  // iterator, and  we have to throw an exception because the return value
  // was not an object.
  ThrowCheckIsObject,

  // These two are similar to ThrowCheckIsObject. We have to throw an exception
  // because the result of a Proxy get trap didn't match the requirements.
  ThrowProxyTrapMustReportSameValue,
  ThrowProxyTrapMustReportUndefined,

  // We have executed code that should be unreachable, and need to assert.
  Unreachable,

  Limit
};

inline const char* BailoutKindString(BailoutKind kind) {
  switch (kind) {
    case BailoutKind::Unknown:
      return "Unknown";
    case BailoutKind::TranspiledCacheIR:
      return "TranspiledCacheIR";
    case BailoutKind::StubFoldingGuardMultipleShapes:
      return "StubFoldingGuardMultipleShapes";
    case BailoutKind::SpeculativePhi:
      return "SpeculativePhi";
    case BailoutKind::TypePolicy:
      return "TypePolicy";
    case BailoutKind::LICM:
      return "LICM";
    case BailoutKind::InstructionReordering:
      return "InstructionReordering";
    case BailoutKind::HoistBoundsCheck:
      return "HoistBoundsCheck";
    case BailoutKind::EagerTruncation:
      return "EagerTruncation";
    case BailoutKind::UnboxFolding:
      return "UnboxFolding";
    case BailoutKind::Inevitable:
      return "Inevitable";
    case BailoutKind::DuringVMCall:
      return "DuringVMCall";
    case BailoutKind::TooManyArguments:
      return "TooManyArguments";
    case BailoutKind::Debugger:
      return "Debugger";
    case BailoutKind::FirstExecution:
      return "FirstExecution";
    case BailoutKind::UninitializedLexical:
      return "UninitializedLexical";
    case BailoutKind::IonExceptionDebugMode:
      return "IonExceptionDebugMode";
    case BailoutKind::Finally:
      return "Finally";
    case BailoutKind::OnStackInvalidation:
      return "OnStackInvalidation";
    case BailoutKind::ThrowCheckIsObject:
      return "ThrowCheckIsObject";
    case BailoutKind::ThrowProxyTrapMustReportSameValue:
      return "ThrowProxyTrapMustReportSameValue";
    case BailoutKind::ThrowProxyTrapMustReportUndefined:
      return "ThrowProxyTrapMustReportUndefined";
    case BailoutKind::Unreachable:
      return "Unreachable";

    case BailoutKind::Limit:
      break;
  }

  MOZ_CRASH("Invalid BailoutKind");
}

static const uint32_t ELEMENT_TYPE_BITS = 5;
static const uint32_t ELEMENT_TYPE_SHIFT = 0;
static const uint32_t ELEMENT_TYPE_MASK = (1 << ELEMENT_TYPE_BITS) - 1;
static const uint32_t VECTOR_TYPE_BITS = 1;
static const uint32_t VECTOR_TYPE_SHIFT =
    ELEMENT_TYPE_BITS + ELEMENT_TYPE_SHIFT;
static const uint32_t VECTOR_TYPE_MASK = (1 << VECTOR_TYPE_BITS) - 1;

// The integer SIMD types have a lot of operations that do the exact same thing
// for signed and unsigned integer types. Sometimes it is simpler to treat
// signed and unsigned integer SIMD types as the same type, using a SimdSign to
// distinguish the few cases where there is a difference.
enum class SimdSign {
  // Signedness is not applicable to this type. (i.e., Float or Bool).
  NotApplicable,
  // Treat as an unsigned integer with a range 0 .. 2^N-1.
  Unsigned,
  // Treat as a signed integer in two's complement encoding.
  Signed,
};

class SimdConstant {
 public:
  enum Type {
    Int8x16,
    Int16x8,
    Int32x4,
    Int64x2,
    Float32x4,
    Float64x2,
    Undefined = -1
  };

  using I8x16 = int8_t[16];
  using I16x8 = int16_t[8];
  using I32x4 = int32_t[4];
  using I64x2 = int64_t[2];
  using F32x4 = float[4];
  using F64x2 = double[2];

 private:
  Type type_;
  union {
    I8x16 i8x16;
    I16x8 i16x8;
    I32x4 i32x4;
    I64x2 i64x2;
    F32x4 f32x4;
    F64x2 f64x2;
  } u;

  bool defined() const { return type_ != Undefined; }

 public:
  // Doesn't have a default constructor, as it would prevent it from being
  // included in unions.

  static SimdConstant Zero() { return SimdConstant::SplatX2(int64_t(0)); }
  static SimdConstant CreateX16(const int8_t* array) {
    SimdConstant cst;
    cst.type_ = Int8x16;
    memcpy(cst.u.i8x16, array, sizeof(cst.u));
    return cst;
  }
  static SimdConstant SplatX16(int8_t v) {
    SimdConstant cst;
    cst.type_ = Int8x16;
    std::fill_n(cst.u.i8x16, 16, v);
    return cst;
  }
  static SimdConstant CreateX8(const int16_t* array) {
    SimdConstant cst;
    cst.type_ = Int16x8;
    memcpy(cst.u.i16x8, array, sizeof(cst.u));
    return cst;
  }
  static SimdConstant SplatX8(int16_t v) {
    SimdConstant cst;
    cst.type_ = Int16x8;
    std::fill_n(cst.u.i16x8, 8, v);
    return cst;
  }
  static SimdConstant CreateX4(const int32_t* array) {
    SimdConstant cst;
    cst.type_ = Int32x4;
    memcpy(cst.u.i32x4, array, sizeof(cst.u));
    return cst;
  }
  static SimdConstant SplatX4(int32_t v) {
    SimdConstant cst;
    cst.type_ = Int32x4;
    std::fill_n(cst.u.i32x4, 4, v);
    return cst;
  }
  static SimdConstant CreateX2(const int64_t* array) {
    SimdConstant cst;
    cst.type_ = Int64x2;
    memcpy(cst.u.i64x2, array, sizeof(cst.u));
    return cst;
  }
  static SimdConstant SplatX2(int64_t v) {
    SimdConstant cst;
    cst.type_ = Int64x2;
    std::fill_n(cst.u.i64x2, 2, v);
    return cst;
  }
  static SimdConstant CreateX4(const float* array) {
    SimdConstant cst;
    cst.type_ = Float32x4;
    memcpy(cst.u.f32x4, array, sizeof(cst.u));
    return cst;
  }
  static SimdConstant SplatX4(float v) {
    SimdConstant cst;
    cst.type_ = Float32x4;
    std::fill_n(cst.u.f32x4, 4, v);
    return cst;
  }
  static SimdConstant CreateX2(const double* array) {
    SimdConstant cst;
    cst.type_ = Float64x2;
    memcpy(cst.u.f64x2, array, sizeof(cst.u));
    return cst;
  }
  static SimdConstant SplatX2(double v) {
    SimdConstant cst;
    cst.type_ = Float64x2;
    std::fill_n(cst.u.f64x2, 2, v);
    return cst;
  }

  // Overloads for use by templates.
  static SimdConstant CreateSimd128(const int8_t* array) {
    return CreateX16(array);
  }
  static SimdConstant CreateSimd128(const int16_t* array) {
    return CreateX8(array);
  }
  static SimdConstant CreateSimd128(const int32_t* array) {
    return CreateX4(array);
  }
  static SimdConstant CreateSimd128(const int64_t* array) {
    return CreateX2(array);
  }
  static SimdConstant CreateSimd128(const float* array) {
    return CreateX4(array);
  }
  static SimdConstant CreateSimd128(const double* array) {
    return CreateX2(array);
  }

  Type type() const {
    MOZ_ASSERT(defined());
    return type_;
  }

  bool isFloatingType() const {
    MOZ_ASSERT(defined());
    return type_ >= Float32x4;
  }

  bool isIntegerType() const {
    MOZ_ASSERT(defined());
    return type_ <= Int64x2;
  }

  // Get the raw bytes of the constant.
  const void* bytes() const { return u.i8x16; }

  const I8x16& asInt8x16() const {
    MOZ_ASSERT(defined() && type_ == Int8x16);
    return u.i8x16;
  }

  const I16x8& asInt16x8() const {
    MOZ_ASSERT(defined() && type_ == Int16x8);
    return u.i16x8;
  }

  const I32x4& asInt32x4() const {
    MOZ_ASSERT(defined() && type_ == Int32x4);
    return u.i32x4;
  }

  const I64x2& asInt64x2() const {
    MOZ_ASSERT(defined() && type_ == Int64x2);
    return u.i64x2;
  }

  const F32x4& asFloat32x4() const {
    MOZ_ASSERT(defined() && type_ == Float32x4);
    return u.f32x4;
  }

  const F64x2& asFloat64x2() const {
    MOZ_ASSERT(defined() && type_ == Float64x2);
    return u.f64x2;
  }

  bool bitwiseEqual(const SimdConstant& rhs) const {
    MOZ_ASSERT(defined() && rhs.defined());
    return memcmp(&u, &rhs.u, sizeof(u)) == 0;
  }

  bool isZeroBits() const {
    MOZ_ASSERT(defined());
    return u.i64x2[0] == 0 && u.i64x2[1] == 0;
  }

  bool isOneBits() const {
    MOZ_ASSERT(defined());
    return ~u.i64x2[0] == 0 && ~u.i64x2[1] == 0;
  }

  // SimdConstant is a HashPolicy.  Currently we discriminate by type, but it
  // may be that we should only be discriminating by int vs float.
  using Lookup = SimdConstant;

  static HashNumber hash(const SimdConstant& val) {
    uint32_t hash = mozilla::HashBytes(&val.u, sizeof(val.u));
    return mozilla::AddToHash(hash, val.type_);
  }

  static bool match(const SimdConstant& lhs, const SimdConstant& rhs) {
    return lhs.type() == rhs.type() && lhs.bitwiseEqual(rhs);
  }
};

enum class IntConversionInputKind { NumbersOnly, Any };

// The ordering of this enumeration is important: Anything < Value is a
// specialized type. Furthermore, anything < String has trivial conversion to
// a number.
enum class MIRType : uint8_t {
  Undefined,
  Null,
  Boolean,
  Int32,
  Int64,
  IntPtr,
  Double,
  Float32,
  // Types above have trivial conversion to a number.
  String,
  Symbol,
  BigInt,
  Simd128,
  // Types above are primitive (including undefined and null).
  Object,
  MagicOptimizedOut,          // JS_OPTIMIZED_OUT magic value.
  MagicHole,                  // JS_ELEMENTS_HOLE magic value.
  MagicIsConstructing,        // JS_IS_CONSTRUCTING magic value.
  MagicUninitializedLexical,  // JS_UNINITIALIZED_LEXICAL magic value.
  // Types above are specialized.
  Value,
  None,            // Invalid, used as a placeholder.
  Slots,           // A slots vector
  Elements,        // An elements vector
  Pointer,         // An opaque pointer that receives no special treatment
  WasmAnyRef,      // Wasm Ref/AnyRef/NullRef: a raw JSObject* or a raw (void*)0
  WasmStructData,  // A WasmStructObject data pointer (to OOL storage only)
  WasmArrayData,   // A WasmArrayObject data pointer (to IL or OOL storage)
  StackResults,    // Wasm multi-value stack result area, which may contain refs
  Shape,           // A Shape pointer.
  Last = Shape
};

static inline MIRType TargetWordMIRType() {
#ifdef JS_64BIT
  return MIRType::Int64;
#else
  return MIRType::Int32;
#endif
}

static inline MIRType MIRTypeFromValueType(JSValueType type) {
  // This function does not deal with magic types. Magic constants should be
  // filtered out in MIRTypeFromValue.
  switch (type) {
    case JSVAL_TYPE_DOUBLE:
      return MIRType::Double;
    case JSVAL_TYPE_INT32:
      return MIRType::Int32;
    case JSVAL_TYPE_UNDEFINED:
      return MIRType::Undefined;
    case JSVAL_TYPE_STRING:
      return MIRType::String;
    case JSVAL_TYPE_SYMBOL:
      return MIRType::Symbol;
    case JSVAL_TYPE_BIGINT:
      return MIRType::BigInt;
    case JSVAL_TYPE_BOOLEAN:
      return MIRType::Boolean;
    case JSVAL_TYPE_NULL:
      return MIRType::Null;
    case JSVAL_TYPE_OBJECT:
      return MIRType::Object;
    case JSVAL_TYPE_UNKNOWN:
      return MIRType::Value;
    default:
      MOZ_CRASH("unexpected jsval type");
  }
}

static inline JSValueType ValueTypeFromMIRType(MIRType type) {
  switch (type) {
    case MIRType::Undefined:
      return JSVAL_TYPE_UNDEFINED;
    case MIRType::Null:
      return JSVAL_TYPE_NULL;
    case MIRType::Boolean:
      return JSVAL_TYPE_BOOLEAN;
    case MIRType::Int32:
      return JSVAL_TYPE_INT32;
    case MIRType::Float32:  // Fall through, there's no JSVAL for Float32
    case MIRType::Double:
      return JSVAL_TYPE_DOUBLE;
    case MIRType::String:
      return JSVAL_TYPE_STRING;
    case MIRType::Symbol:
      return JSVAL_TYPE_SYMBOL;
    case MIRType::BigInt:
      return JSVAL_TYPE_BIGINT;
    case MIRType::MagicOptimizedOut:
    case MIRType::MagicHole:
    case MIRType::MagicIsConstructing:
    case MIRType::MagicUninitializedLexical:
      return JSVAL_TYPE_MAGIC;
    case MIRType::Object:
      return JSVAL_TYPE_OBJECT;
    default:
      break;
  }
  MOZ_CRASH("bad type");
}

static inline JSValueTag MIRTypeToTag(MIRType type) {
  return JSVAL_TYPE_TO_TAG(ValueTypeFromMIRType(type));
}

static inline size_t MIRTypeToSize(MIRType type) {
  switch (type) {
    case MIRType::Int32:
      return 4;
    case MIRType::Int64:
      return 8;
    case MIRType::Float32:
      return 4;
    case MIRType::Double:
      return 8;
    case MIRType::Simd128:
      return 16;
    case MIRType::Pointer:
    case MIRType::WasmAnyRef:
      return sizeof(uintptr_t);
    default:
      MOZ_CRASH("MIRTypeToSize - unhandled case");
  }
}

static inline const char* StringFromMIRType(MIRType type) {
  switch (type) {
    case MIRType::Undefined:
      return "Undefined";
    case MIRType::Null:
      return "Null";
    case MIRType::Boolean:
      return "Bool";
    case MIRType::Int32:
      return "Int32";
    case MIRType::Int64:
      return "Int64";
    case MIRType::IntPtr:
      return "IntPtr";
    case MIRType::Double:
      return "Double";
    case MIRType::Float32:
      return "Float32";
    case MIRType::String:
      return "String";
    case MIRType::Symbol:
      return "Symbol";
    case MIRType::BigInt:
      return "BigInt";
    case MIRType::Object:
      return "Object";
    case MIRType::MagicOptimizedOut:
      return "MagicOptimizedOut";
    case MIRType::MagicHole:
      return "MagicHole";
    case MIRType::MagicIsConstructing:
      return "MagicIsConstructing";
    case MIRType::MagicUninitializedLexical:
      return "MagicUninitializedLexical";
    case MIRType::Value:
      return "Value";
    case MIRType::None:
      return "None";
    case MIRType::Slots:
      return "Slots";
    case MIRType::Elements:
      return "Elements";
    case MIRType::Pointer:
      return "Pointer";
    case MIRType::WasmAnyRef:
      return "WasmAnyRef";
    case MIRType::WasmStructData:
      return "WasmStructData";
    case MIRType::WasmArrayData:
      return "WasmArrayData";
    case MIRType::StackResults:
      return "StackResults";
    case MIRType::Shape:
      return "Shape";
    case MIRType::Simd128:
      return "Simd128";
  }
  MOZ_CRASH("Unknown MIRType.");
}

static inline bool IsIntType(MIRType type) {
  return type == MIRType::Int32 || type == MIRType::Int64 ||
         type == MIRType::IntPtr;
}

static inline bool IsNumberType(MIRType type) {
  return type == MIRType::Int32 || type == MIRType::Double ||
         type == MIRType::Float32 || type == MIRType::Int64 ||
         type == MIRType::IntPtr;
}

static inline bool IsTypeRepresentableAsDouble(MIRType type) {
  return type == MIRType::Int32 || type == MIRType::Double ||
         type == MIRType::Float32;
}

static inline bool IsFloatingPointType(MIRType type) {
  return type == MIRType::Double || type == MIRType::Float32;
}

static inline bool IsNullOrUndefined(MIRType type) {
  return type == MIRType::Null || type == MIRType::Undefined;
}

static inline bool IsMagicType(MIRType type) {
  return type == MIRType::MagicHole || type == MIRType::MagicOptimizedOut ||
         type == MIRType::MagicIsConstructing ||
         type == MIRType::MagicUninitializedLexical;
}

static inline bool IsNonGCThing(MIRType type) {
  return type == MIRType::Undefined || type == MIRType::Null ||
         type == MIRType::Boolean || IsNumberType(type);
}

static inline MIRType ScalarTypeToMIRType(Scalar::Type type) {
  switch (type) {
    case Scalar::Int8:
    case Scalar::Uint8:
    case Scalar::Int16:
    case Scalar::Uint16:
    case Scalar::Int32:
    case Scalar::Uint32:
    case Scalar::Uint8Clamped:
      return MIRType::Int32;
    case Scalar::Int64:
      return MIRType::Int64;
    case Scalar::Float32:
      return MIRType::Float32;
    case Scalar::Float64:
      return MIRType::Double;
    case Scalar::Float16:
    case Scalar::BigInt64:
    case Scalar::BigUint64:
      MOZ_CRASH("NYI");
    case Scalar::Simd128:
      return MIRType::Simd128;
    case Scalar::MaxTypedArrayViewType:
      break;
  }
  MOZ_CRASH("unexpected kind");
}

static constexpr bool NeedsPostBarrier(MIRType type) {
  MOZ_ASSERT(type != MIRType::Value);
  return type == MIRType::Object || type == MIRType::String ||
         type == MIRType::BigInt;
}

#ifdef DEBUG

// Track the pipeline of opcodes which has produced a snapshot.
#  define TRACK_SNAPSHOTS 1

// Make sure registers are not modified between an instruction and
// its OsiPoint.
#  define CHECK_OSIPOINT_REGISTERS 1

#endif  // DEBUG

// Rounding modes for round instructions.
enum class RoundingMode { Down, Up, NearestTiesToEven, TowardsZero };

// If a function contains no calls, we can assume the caller has checked the
// stack limit up to this maximum frame size. This works because the jit stack
// limit has a generous buffer before the real end of the native stack.
static const uint32_t MAX_UNCHECKED_LEAF_FRAME_SIZE = 64;

// Truncating conversion modifiers.
using TruncFlags = uint32_t;
static const TruncFlags TRUNC_UNSIGNED = TruncFlags(1) << 0;
static const TruncFlags TRUNC_SATURATING = TruncFlags(1) << 1;

enum BranchDirection { FALSE_BRANCH, TRUE_BRANCH };

template <typename T>
constexpr T SplatByteToUInt(uint8_t val, uint8_t x) {
  T splatted = val;
  for (; x > 1; x--) {
    splatted |= splatted << 8;
  }
  return splatted;
}

// Resume information for a frame, stored in a resume point.
enum class ResumeMode : uint8_t {
  // Innermost frame. Resume at the next bytecode op when bailing out.
  ResumeAfter,

  // Innermost frame. This resume point captures an additional value
  // that is not on the expression stack. Resume at the next bytecode
  // op when bailing out, but first check that the intermediate value
  // is an object. This is used if calling the |return| method for a
  // CloseIter causes an invalidation bailout.
  ResumeAfterCheckIsObject,

  // Similar to ResumeAfterCheckIsObject, but we must check that the result
  // of a proxy get trap aligns with what the spec requires.
  ResumeAfterCheckProxyGetResult,

  // Innermost frame. Resume at the next bytecode op when bailing out, but the
  // value in the result slot is the internal PropertyIteratorObject created by
  // the Object.keys scalar-replacement optimization instead of the keys array.
  // On bailout we convert it back to the keys array so the internal iterator is
  // never exposed to the baseline frame. This is used when the
  // MObjectToIterator
  // VM call bails out (e.g. an invalidation bailout caused by GC).
  ResumeAfterObjectKeys,

  // Innermost frame. Resume at the current bytecode op when bailing out.
  ResumeAt,

  // Outer frame for an inlined "standard" call at an IsInvokeOp bytecode op.
  InlinedStandardCall,

  // Outer frame for an inlined js::fun_call at an IsInvokeOp bytecode op.
  InlinedFunCall,

  // Outer frame for an inlined getter/setter at a Get*/Set* bytecode op.
  InlinedAccessor,

  Last = InlinedAccessor
};

inline const char* ResumeModeToString(ResumeMode mode) {
  switch (mode) {
    case ResumeMode::ResumeAfter:
      return "ResumeAfter";
    case ResumeMode::ResumeAt:
      return "ResumeAt";
    case ResumeMode::InlinedStandardCall:
      return "InlinedStandardCall";
    case ResumeMode::InlinedFunCall:
      return "InlinedFunCall";
    case ResumeMode::InlinedAccessor:
      return "InlinedAccessor";
    case ResumeMode::ResumeAfterCheckIsObject:
      return "ResumeAfterCheckIsObject";
    case ResumeMode::ResumeAfterCheckProxyGetResult:
      return "ResumeAfterCheckProxyGetResult";
    case ResumeMode::ResumeAfterObjectKeys:
      return "ResumeAfterObjectKeys";
  }
  MOZ_CRASH("Invalid mode");
}

inline bool IsResumeAfter(ResumeMode mode) {
  switch (mode) {
    case ResumeMode::ResumeAfter:
    case ResumeMode::ResumeAfterCheckIsObject:
    case ResumeMode::ResumeAfterCheckProxyGetResult:
    case ResumeMode::ResumeAfterObjectKeys:
      return true;
    default:
      return false;
  }
}

// The number of intermediate values captured by this resume point
// that aren't on the expression stack, but are needed during bailouts.
inline uint32_t NumIntermediateValues(ResumeMode mode) {
  switch (mode) {
    case ResumeMode::ResumeAfterCheckProxyGetResult:
      return 2;
    case ResumeMode::ResumeAfterCheckIsObject:
      return 1;
    default:
      return 0;
  }
}

// Type that represents a constant JS::Value that's either available at compile
// time or (for Cells allocated in the nursery) represented by an index into the
// nursery-Values list.
//
// See the SMDOC comment for WarpObjectField for an explanation of how this is
// used.
class ValueOrNurseryValueIndex {
  Value val_;

  explicit ValueOrNurseryValueIndex(Value val) : val_(val) {}

 public:
  static ValueOrNurseryValueIndex fromValue(Value val) {
    ValueOrNurseryValueIndex res(val);
    MOZ_ASSERT(res.toValue() == val);
    return res;
  }
  static ValueOrNurseryValueIndex fromValueOrNurseryIndex(Value val) {
    return ValueOrNurseryValueIndex(val);
  }
  static ValueOrNurseryValueIndex fromNurseryIndex(uint32_t index) {
    MOZ_RELEASE_ASSERT(index < UINT32_MAX - JS_WHY_MAGIC_COUNT);
    Value v = JS::MagicValueUint32(JS_WHY_MAGIC_COUNT + index);
    ValueOrNurseryValueIndex res(v);
    MOZ_ASSERT(res.toNurseryValueIndex() == index);
    return res;
  }

  bool isValue() const {
    return !val_.isMagic() || val_.magicUint32() < JS_WHY_MAGIC_COUNT;
  }
  Value toValue() const {
    MOZ_ASSERT(isValue());
    return val_;
  }
  uint32_t toNurseryValueIndex() const {
    MOZ_ASSERT(!isValue());
    return val_.magicUint32() - JS_WHY_MAGIC_COUNT;
  }

  uint64_t asRawBits() const { return val_.asRawBits(); }

  bool operator==(ValueOrNurseryValueIndex other) const {
    return val_ == other.val_;
  }
  bool operator!=(ValueOrNurseryValueIndex other) const {
    return val_ != other.val_;
  }
};

}  // namespace jit
}  // namespace js

#endif /* jit_IonTypes_h */
