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

/*
 * Everything needed to build actual MIR instructions: the actual opcodes and
 * instructions, the instruction interface, and use chains.
 */

#ifndef jit_MIR_h
#define jit_MIR_h

#include "mozilla/Array.h"
#include "mozilla/HashFunctions.h"
#ifdef JS_JITSPEW
#  include "mozilla/Attributes.h"  // MOZ_STACK_CLASS
#endif
#include "mozilla/MacroForEach.h"
#ifdef JS_JITSPEW
#  include "mozilla/Sprintf.h"
#  include "mozilla/Vector.h"
#endif

#include <algorithm>
#include <initializer_list>

#include "NamespaceImports.h"

#include "jit/AtomicOp.h"
#include "jit/FixedList.h"
#include "jit/InlineList.h"
#include "jit/JitAllocPolicy.h"
#include "jit/MacroAssembler.h"
#include "jit/MIROpsGenerated.h"
#include "jit/ShuffleAnalysis.h"
#include "jit/TypeData.h"
#include "jit/TypePolicy.h"
#include "js/experimental/JitInfo.h"  // JSJit{Getter,Setter}Op, JSJitInfo
#include "js/HeapAPI.h"
#include "js/ScalarType.h"  // js::Scalar::Type
#include "js/Value.h"
#include "js/Vector.h"
#include "vm/BigIntType.h"
#include "vm/EnvironmentObject.h"
#include "vm/FunctionFlags.h"  // js::FunctionFlags
#include "vm/JSContext.h"
#include "vm/RegExpObject.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmJS.h"  // for WasmInstanceObject

namespace JS {
struct ExpandoAndGeneration;
}

namespace js {

namespace wasm {
class FuncExport;
extern uint32_t MIRTypeToABIResultSize(jit::MIRType);
}  // namespace wasm

class JS_PUBLIC_API GenericPrinter;
class NativeIteratorListHead;
class StringObject;

enum class UnaryMathFunction : uint8_t;

bool CurrentThreadIsIonCompiling();

namespace jit {

class CallInfo;

#ifdef JS_JITSPEW
// Helper for debug printing.  Avoids creating a MIR.h <--> MIRGraph.h cycle.
// Implementation of this needs to see inside `MBasicBlock`; that is possible
// in MIR.cpp since it also includes MIRGraph.h, whereas this file does not.
class MBasicBlock;
uint32_t GetMBasicBlockId(const MBasicBlock* block);

// Helper class for debug printing.  This class allows `::getExtras` methods
// to add strings to be printed, on a per-MIR-node basis.  The strings are
// copied into storage owned by this class when `::add` is called, so the
// `::getExtras` methods do not need to be concerned about storage management.
class MOZ_STACK_CLASS ExtrasCollector {
  mozilla::Vector<UniqueChars, 4> strings_;

 public:
  // Add `str` to the collection.  A copy, owned by this object, is made.  In
  // case of OOM the call has no effect.
  void add(const char* str) {
    UniqueChars dup = DuplicateString(str);
    if (dup) {
      (void)strings_.append(std::move(dup));
    }
  }
  size_t count() const { return strings_.length(); }
  UniqueChars get(size_t ix) { return std::move(strings_[ix]); }
};
#endif

// Forward declarations of MIR types.
#define FORWARD_DECLARE(op) class M##op;
MIR_OPCODE_LIST(FORWARD_DECLARE)
#undef FORWARD_DECLARE

// MDefinition visitor which ignores non-overloaded visit functions.
class MDefinitionVisitorDefaultNoop {
 public:
#define VISIT_INS(op) \
  void visit##op(M##op*) {}
  MIR_OPCODE_LIST(VISIT_INS)
#undef VISIT_INS
};

class BytecodeSite;
class CompactBufferWriter;
class Range;

#define MIR_FLAG_LIST(_)                                                       \
  _(InWorklist)                                                                \
  _(EmittedAtUses)                                                             \
  _(Commutative)                                                               \
  _(Movable) /* Allow passes like LICM to move this instruction */             \
  _(Lowered) /* (Debug only) has a virtual register */                         \
  _(Guard)   /* Not removable if uses == 0 */                                  \
                                                                               \
  /* Flag an instruction to be considered as a Guard if the instructions       \
   * bails out on some inputs.                                                 \
   *                                                                           \
   * Some optimizations can replace an instruction, and leave its operands     \
   * unused. When the type information of the operand got used as a            \
   * predicate of the transformation, then we have to flag the operands as     \
   * GuardRangeBailouts.                                                       \
   *                                                                           \
   * This flag prevents further optimization of instructions, which            \
   * might remove the run-time checks (bailout conditions) used as a           \
   * predicate of the previous transformation.                                 \
   */                                                                          \
  _(GuardRangeBailouts)                                                        \
                                                                               \
  /* Some instructions have uses that aren't directly represented in the       \
   * graph, and need to be handled specially. As an example, this is used to   \
   * keep the flagged instruction in resume points, not substituting with an   \
   * UndefinedValue. This can be used by call inlining when a function         \
   * argument is not used by the inlined instructions. It is also used         \
   * to annotate instructions which were used in removed branches.             \
   */                                                                          \
  _(ImplicitlyUsed)                                                            \
                                                                               \
  /* The instruction has been marked dead for lazy removal from resume         \
   * points.                                                                   \
   */                                                                          \
  _(Unused)                                                                    \
                                                                               \
  /* Marks if the current instruction should go to the bailout paths instead   \
   * of producing code as part of the control flow.  This flag can only be set \
   * on instructions which are only used by ResumePoint or by other flagged    \
   * instructions.                                                             \
   */                                                                          \
  _(RecoveredOnBailout)                                                        \
                                                                               \
  /* Some instructions might represent an object, but the memory of these      \
   * objects might be incomplete if we have not recovered all the stores which \
   * were supposed to happen before. This flag is used to annotate             \
   * instructions which might return a pointer to a memory area which is not   \
   * yet fully initialized. This flag is used to ensure that stores are        \
   * executed before returning the value.                                      \
   */                                                                          \
  _(IncompleteObject)                                                          \
                                                                               \
  /* For WebAssembly, there are functions with multiple results.  Instead of   \
   * having the results defined by one call instruction, they are instead      \
   * captured in subsequent result capture instructions, because modelling     \
   * multi-value results in Ion is too complicated.  However since they        \
   * capture ambient live registers, it would be an error to move an unrelated \
   * instruction between the call and the result capture.  This flag is used   \
   * to prevent code motion from moving instructions in invalid ways.          \
   */                                                                          \
  _(CallResultCapture)                                                         \
                                                                               \
  /* The current instruction got discarded from the MIR Graph. This is useful  \
   * when we want to iterate over resume points and instructions, while        \
   * handling instructions which are discarded without reporting to the        \
   * iterator.                                                                 \
   */                                                                          \
  _(Discarded)

class MDefinition;
class MInstruction;
class MBasicBlock;
class MNode;
class MUse;
class MPhi;
class MIRGraph;
class MResumePoint;
class MControlInstruction;

// Represents a use of a node.
class MUse : public TempObject, public InlineListNode<MUse> {
  // Grant access to setProducerUnchecked.
  friend class MDefinition;
  friend class MPhi;

  MDefinition* producer_;  // MDefinition that is being used.
  MNode* consumer_;        // The node that is using this operand.

  // Low-level unchecked edit method for replaceAllUsesWith and
  // MPhi::removeOperand. This doesn't update use lists!
  // replaceAllUsesWith and MPhi::removeOperand do that manually.
  void setProducerUnchecked(MDefinition* producer) {
    MOZ_ASSERT(consumer_);
    MOZ_ASSERT(producer_);
    MOZ_ASSERT(producer);
    producer_ = producer;
  }

 public:
  // Default constructor for use in vectors.
  MUse() : producer_(nullptr), consumer_(nullptr) {}

  // Move constructor for use in vectors. When an MUse is moved, it stays
  // in its containing use list.
  MUse(MUse&& other)
      : InlineListNode<MUse>(std::move(other)),
        producer_(other.producer_),
        consumer_(other.consumer_) {}

  // Construct an MUse initialized with |producer| and |consumer|.
  MUse(MDefinition* producer, MNode* consumer) {
    initUnchecked(producer, consumer);
  }

  // Set this use, which was previously clear.
  inline void init(MDefinition* producer, MNode* consumer);
  // Like init, but works even when the use contains uninitialized data.
  inline void initUnchecked(MDefinition* producer, MNode* consumer);
  // Like initUnchecked, but set the producer to nullptr.
  inline void initUncheckedWithoutProducer(MNode* consumer);
  // Set this use, which was not previously clear.
  inline void replaceProducer(MDefinition* producer);
  // Clear this use.
  inline void releaseProducer();

  MDefinition* producer() const {
    MOZ_ASSERT(producer_ != nullptr);
    return producer_;
  }
  bool hasProducer() const { return producer_ != nullptr; }
  MNode* consumer() const {
    MOZ_ASSERT(consumer_ != nullptr);
    return consumer_;
  }

#ifdef DEBUG
  // Return the operand index of this MUse in its consumer. This is DEBUG-only
  // as normal code should instead call indexOf on the cast consumer directly,
  // to allow it to be devirtualized and inlined.
  size_t index() const;
#endif
};

using MUseIterator = InlineList<MUse>::iterator;

// A node is an entry in the MIR graph. It has two kinds:
//   MInstruction: an instruction which appears in the IR stream.
//   MResumePoint: a list of instructions that correspond to the state of the
//                 interpreter/Baseline stack.
//
// Nodes can hold references to MDefinitions. Each MDefinition has a list of
// nodes holding such a reference (its use chain).
class MNode : public TempObject {
 protected:
  enum class Kind { Definition = 0, ResumePoint };

 private:
  static const uintptr_t KindMask = 0x1;
  uintptr_t blockAndKind_;

  Kind kind() const { return Kind(blockAndKind_ & KindMask); }

 protected:
  explicit MNode(const MNode& other) : blockAndKind_(other.blockAndKind_) {}

  MNode(MBasicBlock* block, Kind kind) { setBlockAndKind(block, kind); }

  void setBlockAndKind(MBasicBlock* block, Kind kind) {
    blockAndKind_ = uintptr_t(block) | uintptr_t(kind);
    MOZ_ASSERT(this->block() == block);
  }

  MBasicBlock* definitionBlock() const {
    MOZ_ASSERT(isDefinition());
    static_assert(unsigned(Kind::Definition) == 0,
                  "Code below relies on low bit being 0");
    return reinterpret_cast<MBasicBlock*>(blockAndKind_);
  }
  MBasicBlock* resumePointBlock() const {
    MOZ_ASSERT(isResumePoint());
    static_assert(unsigned(Kind::ResumePoint) == 1,
                  "Code below relies on low bit being 1");
    // Use a subtraction: if the caller does block()->foo, the compiler
    // will be able to fold it with the load.
    return reinterpret_cast<MBasicBlock*>(blockAndKind_ - 1);
  }

 public:
  // Returns the definition at a given operand.
  virtual MDefinition* getOperand(size_t index) const = 0;
  virtual size_t numOperands() const = 0;
  virtual size_t indexOf(const MUse* u) const = 0;

  bool isDefinition() const { return kind() == Kind::Definition; }
  bool isResumePoint() const { return kind() == Kind::ResumePoint; }
  MBasicBlock* block() const {
    return reinterpret_cast<MBasicBlock*>(blockAndKind_ & ~KindMask);
  }
  MBasicBlock* caller() const;

  // Sets an already set operand, updating use information. If you're looking
  // for setOperand, this is probably what you want.
  virtual void replaceOperand(size_t index, MDefinition* operand) = 0;

  // Resets the operand to an uninitialized state, breaking the link
  // with the previous operand's producer.
  void releaseOperand(size_t index) { getUseFor(index)->releaseProducer(); }
  bool hasOperand(size_t index) const {
    return getUseFor(index)->hasProducer();
  }

  inline MDefinition* toDefinition();
  inline MResumePoint* toResumePoint();

  [[nodiscard]] virtual bool writeRecoverData(
      CompactBufferWriter& writer) const;

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

 protected:
  // Need visibility on getUseFor to avoid O(n^2) complexity.
  friend void AssertBasicGraphCoherency(MIRGraph& graph, bool force);

  // Gets the MUse corresponding to given operand.
  virtual MUse* getUseFor(size_t index) = 0;
  virtual const MUse* getUseFor(size_t index) const = 0;
};

class AliasSet {
 private:
  uint32_t flags_;

 public:
  enum Flag {
    None_ = 0,
    ObjectFields = 1 << 0,      // shape, class, slots, length etc.
    Element = 1 << 1,           // A Value member of obj->elements or
                                // a typed object.
    UnboxedElement = 1 << 2,    // An unboxed scalar or reference member of
                                // typed object.
    DynamicSlot = 1 << 3,       // A Value member of obj->slots.
    FixedSlot = 1 << 4,         // A Value member of obj->fixedSlots().
    DOMProperty = 1 << 5,       // A DOM property
    WasmInstanceData = 1 << 6,  // An asm.js/wasm private global var
    WasmHeap = 1 << 7,          // An asm.js/wasm heap load
    WasmHeapMeta = 1 << 8,      // The asm.js/wasm heap base pointer and
                                // bounds check limit, in Instance.
    ArrayBufferViewLengthOrOffset =
        1 << 9,                  // An array buffer view's length or byteOffset
    WasmGlobalCell = 1 << 10,    // A wasm global cell
    WasmTableElement = 1 << 11,  // An element of a wasm table
    WasmTableMeta = 1 << 12,     // A wasm table elements pointer and
                                 // length field, in instance data.
    WasmStackResult = 1 << 13,   // A stack result from the current function

    // JSContext's exception state. This is used on instructions like MThrow
    // or MNewArrayDynamicLength that throw exceptions (other than OOM) but have
    // no other side effect, to ensure that they get their own up-to-date resume
    // point. (This resume point will be used when constructing the Baseline
    // frame during exception bailouts.)
    ExceptionState = 1 << 14,

    // Used for instructions that load the privateSlot of DOM proxies and
    // the ExpandoAndGeneration.
    DOMProxyExpando = 1 << 15,

    // Hash table of a Map or Set object.
    MapOrSetHashTable = 1 << 16,

    // Internal state of the random number generator
    RNG = 1 << 17,

    // The pendingException slot on the wasm instance object.
    WasmPendingException = 1 << 18,

    // The fuzzilliHash slot
    FuzzilliHash = 1 << 19,

    // The WasmStructObject::inlineData_[..] storage area
    WasmStructInlineDataArea = 1 << 20,

    // The WasmStructObject::outlineData_ pointer only
    WasmStructOutlineDataPointer = 1 << 21,

    // The malloc'd block that WasmStructObject::outlineData_ points at
    WasmStructOutlineDataArea = 1 << 22,

    // The WasmArrayObject::numElements_ field
    WasmArrayNumElements = 1 << 23,

    // The WasmArrayObject::data_ pointer only
    WasmArrayDataPointer = 1 << 24,

    // The malloc'd block that WasmArrayObject::data_ points at
    WasmArrayDataArea = 1 << 25,

    // The generation counter associated with the global object
    GlobalGenerationCounter = 1 << 26,

    Last = GlobalGenerationCounter,

    Any = Last | (Last - 1),
    NumCategories = 27,

    // Indicates load or store.
    Store_ = 1 << 31
  };

  static_assert((1 << NumCategories) - 1 == Any,
                "NumCategories must include all flags present in Any");

  explicit AliasSet(uint32_t flags) : flags_(flags) {}

 public:
  inline bool isNone() const { return flags_ == None_; }
  uint32_t flags() const { return flags_ & Any; }
  inline bool isStore() const { return !!(flags_ & Store_); }
  inline bool isLoad() const { return !isStore() && !isNone(); }
  inline AliasSet operator|(const AliasSet& other) const {
    return AliasSet(flags_ | other.flags_);
  }
  inline AliasSet operator&(const AliasSet& other) const {
    return AliasSet(flags_ & other.flags_);
  }
  static AliasSet None() { return AliasSet(None_); }
  static AliasSet Load(uint32_t flags) {
    MOZ_ASSERT(flags && !(flags & Store_));
    return AliasSet(flags);
  }
  static AliasSet Store(uint32_t flags) {
    MOZ_ASSERT(flags && !(flags & Store_));
    return AliasSet(flags | Store_);
  }
};

typedef Vector<MDefinition*, 6, JitAllocPolicy> MDefinitionVector;
typedef Vector<MInstruction*, 6, JitAllocPolicy> MInstructionVector;

// When a floating-point value is used by nodes which would prefer to
// receive integer inputs, we may be able to help by computing our result
// into an integer directly.
//
// A value can be truncated in 4 differents ways:
//   1. Ignore Infinities (x / 0 --> 0).
//   2. Ignore overflow (INT_MIN / -1 == (INT_MAX + 1) --> INT_MIN)
//   3. Ignore negative zeros. (-0 --> 0)
//   4. Ignore remainder. (3 / 4 --> 0)
//
// Indirect truncation is used to represent that we are interested in the
// truncated result, but only if it can safely flow into operations which
// are computed modulo 2^32, such as (2) and (3). Infinities are not safe,
// as they would have absorbed other math operations. Remainders are not
// safe, as fractions can be scaled up by multiplication.
//
// Division is a particularly interesting node here because it covers all 4
// cases even when its own operands are integers.
//
// Note that these enum values are ordered from least value-modifying to
// most value-modifying, and code relies on this ordering.
enum class TruncateKind {
  // No correction.
  NoTruncate = 0,
  // An integer is desired, but we can't skip bailout checks.
  TruncateAfterBailouts = 1,
  // The value will be truncated after some arithmetic (see above).
  IndirectTruncate = 2,
  // Direct and infallible truncation to int32.
  Truncate = 3
};

// An MDefinition is an SSA name.
class MDefinition : public MNode {
  friend class MBasicBlock;

 public:
  enum class Opcode : uint16_t {
#define DEFINE_OPCODES(op) op,
    MIR_OPCODE_LIST(DEFINE_OPCODES)
#undef DEFINE_OPCODES
  };

 private:
  InlineList<MUse> uses_;  // Use chain.
  uint32_t id_;            // Instruction ID, which after block re-ordering
                           // is sorted within a basic block.
  Opcode op_;              // Opcode.
  uint16_t flags_;         // Bit flags.
  Range* range_;           // Any computed range for this def.
  union {
    MDefinition*
        loadDependency_;  // Implicit dependency (store, call, etc.) of this
                          // instruction. Used by alias analysis, GVN and LICM.
    uint32_t virtualRegister_;  // Used by lowering to map definitions to
                                // virtual registers.
  };

  // Track bailouts by storing the current pc in MIR instruction. Also used
  // for profiling and keeping track of what the last known pc was.
  const BytecodeSite* trackedSite_;

  // If we generate a bailout path for this instruction, this is the
  // bailout kind that will be encoded in the snapshot. When we bail out,
  // FinishBailoutToBaseline may take action based on the bailout kind to
  // prevent bailout loops. (For example, if an instruction bails out after
  // being hoisted by LICM, we will disable LICM when recompiling the script.)
  BailoutKind bailoutKind_;

  MIRType resultType_;  // Representation of result type.

 private:
  enum Flag {
    None = 0,
#define DEFINE_FLAG(flag) flag,
    MIR_FLAG_LIST(DEFINE_FLAG)
#undef DEFINE_FLAG
        Total
  };

  bool hasFlags(uint32_t flags) const { return (flags_ & flags) == flags; }
  void removeFlags(uint32_t flags) { flags_ &= ~flags; }
  void setFlags(uint32_t flags) { flags_ |= flags; }

  // Calling isDefinition or isResumePoint on MDefinition is unnecessary.
  bool isDefinition() const = delete;
  bool isResumePoint() const = delete;

 protected:
  void setInstructionBlock(MBasicBlock* block, const BytecodeSite* site) {
    MOZ_ASSERT(isInstruction());
    setBlockAndKind(block, Kind::Definition);
    setTrackedSite(site);
  }

  void setPhiBlock(MBasicBlock* block) {
    MOZ_ASSERT(isPhi());
    setBlockAndKind(block, Kind::Definition);
  }

  static HashNumber addU32ToHash(HashNumber hash, uint32_t data) {
    return data + (hash << 6) + (hash << 16) - hash;
  }

  static HashNumber addU64ToHash(HashNumber hash, uint64_t data) {
    hash = addU32ToHash(hash, uint32_t(data));
    hash = addU32ToHash(hash, uint32_t(data >> 32));
    return hash;
  }

 public:
  explicit MDefinition(Opcode op)
      : MNode(nullptr, Kind::Definition),
        id_(0),
        op_(op),
        flags_(0),
        range_(nullptr),
        loadDependency_(nullptr),
        trackedSite_(nullptr),
        bailoutKind_(BailoutKind::Unknown),
        resultType_(MIRType::None) {}

  // Copying a definition leaves the list of uses empty.
  explicit MDefinition(const MDefinition& other)
      : MNode(other),
        id_(0),
        op_(other.op_),
        flags_(other.flags_),
        range_(other.range_),
        loadDependency_(other.loadDependency_),
        trackedSite_(other.trackedSite_),
        bailoutKind_(other.bailoutKind_),
        resultType_(other.resultType_) {}

  Opcode op() const { return op_; }

#ifdef JS_JITSPEW
  const char* opName() const;
  void printName(GenericPrinter& out) const;
  static void PrintOpcodeName(GenericPrinter& out, Opcode op);
  virtual void printOpcode(GenericPrinter& out) const;
  void dump(GenericPrinter& out) const override;
  void dump() const override;
  void dumpLocation(GenericPrinter& out) const;
  void dumpLocation() const;
  // Dump any other stuff the node wants to have printed in `extras`.  The
  // added strings are copied, with the `ExtrasCollector` taking ownership of
  // the copies.
  virtual void getExtras(ExtrasCollector* extras) {}
#endif

  // Also for LICM. Test whether this definition is likely to be a call, which
  // would clobber all or many of the floating-point registers, such that
  // hoisting floating-point constants out of containing loops isn't likely to
  // be worthwhile.
  virtual bool possiblyCalls() const { return false; }

  MBasicBlock* block() const { return definitionBlock(); }

 private:
#ifdef DEBUG
  bool trackedSiteMatchesBlock(const BytecodeSite* site) const;
#endif

  void setTrackedSite(const BytecodeSite* site) {
    MOZ_ASSERT(site);
    MOZ_ASSERT(trackedSiteMatchesBlock(site),
               "tracked bytecode site should match block bytecode site");
    trackedSite_ = site;
  }

 public:
  const BytecodeSite* trackedSite() const {
    MOZ_ASSERT(trackedSite_,
               "missing tracked bytecode site; node not assigned to a block?");
    MOZ_ASSERT(trackedSiteMatchesBlock(trackedSite_),
               "tracked bytecode site should match block bytecode site");
    return trackedSite_;
  }

  BailoutKind bailoutKind() const { return bailoutKind_; }
  void setBailoutKind(BailoutKind kind) { bailoutKind_ = kind; }

  // Return the range of this value, *before* any bailout checks. Contrast
  // this with the type() method, and the Range constructor which takes an
  // MDefinition*, which describe the value *after* any bailout checks.
  //
  // Warning: Range analysis is removing the bit-operations such as '| 0' at
  // the end of the transformations. Using this function to analyse any
  // operands after the truncate phase of the range analysis will lead to
  // errors. Instead, one should define the collectRangeInfoPreTrunc() to set
  // the right set of flags which are dependent on the range of the inputs.
  Range* range() const {
    MOZ_ASSERT(type() != MIRType::None);
    return range_;
  }
  void setRange(Range* range) {
    MOZ_ASSERT(type() != MIRType::None);
    range_ = range;
  }

  virtual HashNumber valueHash() const;
  virtual bool congruentTo(const MDefinition* ins) const { return false; }
  const MDefinition* skipObjectGuards() const;
  bool congruentIfOperandsEqual(const MDefinition* ins) const;
  virtual MDefinition* foldsTo(TempAllocator& alloc);
  virtual void analyzeEdgeCasesForward();
  virtual void analyzeEdgeCasesBackward();

  // |canTruncate| reports if this instruction supports truncation. If
  // |canTruncate| function returns true, then the |truncate| function is
  // called on the same instruction to mutate the instruction, such as updating
  // the return type, the range and the specialization of the instruction.
  virtual bool canTruncate() const;
  virtual void truncate(TruncateKind kind);

  // Determine what kind of truncate this node prefers for the operand at the
  // given index.
  virtual TruncateKind operandTruncateKind(size_t index) const;

  // Compute an absolute or symbolic range for the value of this node.
  virtual void computeRange(TempAllocator& alloc) {}

  // Collect information from the pre-truncated ranges.
  virtual void collectRangeInfoPreTrunc() {}

  uint32_t id() const {
    MOZ_ASSERT(block());
    return id_;
  }
  void setId(uint32_t id) { id_ = id; }

#define FLAG_ACCESSOR(flag)                            \
  bool is##flag() const {                              \
    static_assert(Flag::Total <= sizeof(flags_) * 8,   \
                  "Flags should fit in flags_ field"); \
    return hasFlags(1 << flag);                        \
  }                                                    \
  void set##flag() {                                   \
    MOZ_ASSERT(!hasFlags(1 << flag));                  \
    setFlags(1 << flag);                               \
  }                                                    \
  void setNot##flag() {                                \
    MOZ_ASSERT(hasFlags(1 << flag));                   \
    removeFlags(1 << flag);                            \
  }                                                    \
  void set##flag##Unchecked() { setFlags(1 << flag); } \
  void setNot##flag##Unchecked() { removeFlags(1 << flag); }

  MIR_FLAG_LIST(FLAG_ACCESSOR)
#undef FLAG_ACCESSOR

  // Return the type of this value. This may be speculative, and enforced
  // dynamically with the use of bailout checks. If all the bailout checks
  // pass, the value will have this type.
  //
  // Unless this is an MUrsh that has bailouts disabled, which, as a special
  // case, may return a value in (INT32_MAX,UINT32_MAX] even when its type()
  // is MIRType::Int32.
  MIRType type() const { return resultType_; }

  bool mightBeType(MIRType type) const {
    MOZ_ASSERT(type != MIRType::Value);

    if (type == this->type()) {
      return true;
    }

    if (this->type() == MIRType::Value) {
      return true;
    }

    return false;
  }

  bool mightBeMagicType() const;

  // Return true if the result-set types are a subset of the given types.
  bool definitelyType(std::initializer_list<MIRType> types) const;

  // Float32 specialization operations (see big comment in IonAnalysis before
  // the Float32 specialization algorithm).
  virtual bool isFloat32Commutative() const { return false; }
  virtual bool canProduceFloat32() const { return false; }
  virtual bool canConsumeFloat32(MUse* use) const { return false; }
  virtual void trySpecializeFloat32(TempAllocator& alloc) {}
#ifdef DEBUG
  // Used during the pass that checks that Float32 flow into valid MDefinitions
  virtual bool isConsistentFloat32Use(MUse* use) const {
    return type() == MIRType::Float32 || canConsumeFloat32(use);
  }
#endif

  // Returns the beginning of this definition's use chain.
  MUseIterator usesBegin() const { return uses_.begin(); }

  // Returns the end of this definition's use chain.
  MUseIterator usesEnd() const { return uses_.end(); }

  bool canEmitAtUses() const { return !isEmittedAtUses(); }

  // Removes a use at the given position
  void removeUse(MUse* use) { uses_.remove(use); }

#if defined(DEBUG) || defined(JS_JITSPEW)
  // Number of uses of this instruction. This function is only available
  // in DEBUG mode since it requires traversing the list. Most users should
  // use hasUses() or hasOneUse() instead.
  size_t useCount() const;

  // Number of uses of this instruction (only counting MDefinitions, ignoring
  // MResumePoints). This function is only available in DEBUG mode since it
  // requires traversing the list. Most users should use hasUses() or
  // hasOneUse() instead.
  size_t defUseCount() const;
#endif

  // Test whether this MDefinition has exactly one use.
  bool hasOneUse() const;

  // Test whether this MDefinition has exactly one use.
  // (only counting MDefinitions, ignoring MResumePoints)
  bool hasOneDefUse() const;

  // Test whether this MDefinition has at least one use.
  // (only counting MDefinitions, ignoring MResumePoints)
  bool hasDefUses() const;

  // Test whether this MDefinition has at least one non-recovered use.
  // (only counting MDefinitions, ignoring MResumePoints)
  bool hasLiveDefUses() const;

  bool hasUses() const { return !uses_.empty(); }

  // If this MDefinition has a single use (ignoring MResumePoints), returns that
  // use's definition. Else returns nullptr.
  MDefinition* maybeSingleDefUse() const;

  // Returns the most recently added use (ignoring MResumePoints) for this
  // MDefinition. Returns nullptr if there are no uses. Note that this relies on
  // addUse adding new uses to the front of the list, and should only be called
  // during MIR building (before optimization passes make changes to the uses).
  MDefinition* maybeMostRecentlyAddedDefUse() const;

  void addUse(MUse* use) {
    MOZ_ASSERT(use->producer() == this);
    uses_.pushFront(use);
  }
  void addUseUnchecked(MUse* use) {
    MOZ_ASSERT(use->producer() == this);
    uses_.pushFrontUnchecked(use);
  }
  void replaceUse(MUse* old, MUse* now) {
    MOZ_ASSERT(now->producer() == this);
    uses_.replace(old, now);
  }

  // Replace the current instruction by a dominating instruction |dom| in all
  // uses of the current instruction.
  void replaceAllUsesWith(MDefinition* dom);

  // Like replaceAllUsesWith, but doesn't set ImplicitlyUsed on |this|'s
  // operands.
  void justReplaceAllUsesWith(MDefinition* dom);

  // Replace the current instruction by an optimized-out constant in all uses
  // of the current instruction. Note, that optimized-out constant should not
  // be observed, and thus they should not flow in any computation.
  [[nodiscard]] bool optimizeOutAllUses(TempAllocator& alloc);

  // Replace the current instruction by a dominating instruction |dom| in all
  // instruction, but keep the current instruction for resume point and
  // instruction which are recovered on bailouts.
  void replaceAllLiveUsesWith(MDefinition* dom);

  void setVirtualRegister(uint32_t vreg) {
    virtualRegister_ = vreg;
    setLoweredUnchecked();
  }
  uint32_t virtualRegister() const {
    MOZ_ASSERT(isLowered());
    return virtualRegister_;
  }

 public:
  // Opcode testing and casts.
  template <typename MIRType>
  bool is() const {
    return op() == MIRType::classOpcode;
  }
  template <typename MIRType>
  MIRType* to() {
    MOZ_ASSERT(this->is<MIRType>());
    return static_cast<MIRType*>(this);
  }
  template <typename MIRType>
  const MIRType* to() const {
    MOZ_ASSERT(this->is<MIRType>());
    return static_cast<const MIRType*>(this);
  }
#define OPCODE_CASTS(opcode)                                \
  bool is##opcode() const { return this->is<M##opcode>(); } \
  M##opcode* to##opcode() { return this->to<M##opcode>(); } \
  const M##opcode* to##opcode() const { return this->to<M##opcode>(); }
  MIR_OPCODE_LIST(OPCODE_CASTS)
#undef OPCODE_CASTS

  inline MConstant* maybeConstantValue();

  inline MInstruction* toInstruction();
  inline const MInstruction* toInstruction() const;
  bool isInstruction() const { return !isPhi(); }

  virtual bool isControlInstruction() const { return false; }
  inline MControlInstruction* toControlInstruction();

  void setResultType(MIRType type) { resultType_ = type; }
  virtual AliasSet getAliasSet() const {
    // Instructions are effectful by default.
    return AliasSet::Store(AliasSet::Any);
  }

#ifdef DEBUG
  bool hasDefaultAliasSet() const {
    AliasSet set = getAliasSet();
    return set.isStore() && set.flags() == AliasSet::Flag::Any;
  }
#endif

  MDefinition* dependency() const {
    if (getAliasSet().isStore()) {
      return nullptr;
    }
    return loadDependency_;
  }
  void setDependency(MDefinition* dependency) {
    MOZ_ASSERT(!getAliasSet().isStore());
    loadDependency_ = dependency;
  }
  bool isEffectful() const { return getAliasSet().isStore(); }

#ifdef DEBUG
  bool needsResumePoint() const {
    // Return whether this instruction should have its own resume point.
    return isEffectful();
  }
#endif

  enum class AliasType : uint32_t { NoAlias = 0, MayAlias = 1, MustAlias = 2 };
  virtual AliasType mightAlias(const MDefinition* store) const {
    // Return whether this load may depend on the specified store, given
    // that the alias sets intersect. This may be refined to exclude
    // possible aliasing in cases where alias set flags are too imprecise.
    if (!(getAliasSet().flags() & store->getAliasSet().flags())) {
      return AliasType::NoAlias;
    }
    MOZ_ASSERT(!isEffectful() && store->isEffectful());
    return AliasType::MayAlias;
  }

  virtual bool canRecoverOnBailout() const { return false; }
};

// An MUseDefIterator walks over uses in a definition, skipping any use that is
// not a definition. Items from the use list must not be deleted during
// iteration.
class MUseDefIterator {
  const MDefinition* def_;
  MUseIterator current_;

  MUseIterator search(MUseIterator start) {
    MUseIterator i(start);
    for (; i != def_->usesEnd(); i++) {
      if (i->consumer()->isDefinition()) {
        return i;
      }
    }
    return def_->usesEnd();
  }

 public:
  explicit MUseDefIterator(const MDefinition* def)
      : def_(def), current_(search(def->usesBegin())) {}

  explicit operator bool() const { return current_ != def_->usesEnd(); }
  MUseDefIterator operator++() {
    MOZ_ASSERT(current_ != def_->usesEnd());
    ++current_;
    current_ = search(current_);
    return *this;
  }
  MUseDefIterator operator++(int) {
    MUseDefIterator old(*this);
    operator++();
    return old;
  }
  MUse* use() const { return *current_; }
  MDefinition* def() const { return current_->consumer()->toDefinition(); }
};

// Helper class to check that GC pointers embedded in MIR instructions are not
// in the nursery. Off-thread compilation and nursery GCs can happen in
// parallel. Nursery pointers are handled with MNurseryObject and the
// nurseryObjects lists in WarpSnapshot and IonScript.
//
// These GC things are rooted through the WarpSnapshot. Compacting GCs cancel
// off-thread compilations.
template <typename T>
class CompilerGCPointer {
  js::gc::Cell* ptr_;

 public:
  explicit CompilerGCPointer(T ptr) : ptr_(ptr) {
    MOZ_ASSERT_IF(ptr, !IsInsideNursery(ptr));
    MOZ_ASSERT_IF(!CurrentThreadIsIonCompiling(), TlsContext.get()->suppressGC);
  }

  operator T() const { return static_cast<T>(ptr_); }
  T operator->() const { return static_cast<T>(ptr_); }

 private:
  CompilerGCPointer() = delete;
  CompilerGCPointer(const CompilerGCPointer<T>&) = delete;
  CompilerGCPointer<T>& operator=(const CompilerGCPointer<T>&) = delete;
};

using CompilerObject = CompilerGCPointer<JSObject*>;
using CompilerNativeObject = CompilerGCPointer<NativeObject*>;
using CompilerFunction = CompilerGCPointer<JSFunction*>;
using CompilerBaseScript = CompilerGCPointer<BaseScript*>;
using CompilerPropertyName = CompilerGCPointer<PropertyName*>;
using CompilerShape = CompilerGCPointer<Shape*>;
using CompilerGetterSetter = CompilerGCPointer<GetterSetter*>;

// An instruction is an SSA name that is inserted into a basic block's IR
// stream.
class MInstruction : public MDefinition, public InlineListNode<MInstruction> {
  MResumePoint* resumePoint_;

 protected:
  // All MInstructions are using the "MFoo::New(alloc)" notation instead of
  // the TempObject new operator. This code redefines the new operator as
  // protected, and delegates to the TempObject new operator. Thus, the
  // following code prevents calls to "new(alloc) MFoo" outside the MFoo
  // members.
  inline void* operator new(size_t nbytes,
                            TempAllocator::Fallible view) noexcept(true) {
    return TempObject::operator new(nbytes, view);
  }
  inline void* operator new(size_t nbytes, TempAllocator& alloc) {
    return TempObject::operator new(nbytes, alloc);
  }
  template <class T>
  inline void* operator new(size_t nbytes, T* pos) {
    return TempObject::operator new(nbytes, pos);
  }

 public:
  explicit MInstruction(Opcode op) : MDefinition(op), resumePoint_(nullptr) {}

  // Copying an instruction leaves the resume point as empty.
  explicit MInstruction(const MInstruction& other)
      : MDefinition(other), resumePoint_(nullptr) {}

  // Convenient function used for replacing a load by the value of the store
  // if the types are match, and boxing the value if they do not match.
  MDefinition* foldsToStore(TempAllocator& alloc);

  void setResumePoint(MResumePoint* resumePoint);
  void stealResumePoint(MInstruction* other);

  void moveResumePointAsEntry();
  void clearResumePoint();
  MResumePoint* resumePoint() const { return resumePoint_; }

  // For instructions which can be cloned with new inputs, with all other
  // information being the same. clone() implementations do not need to worry
  // about cloning generic MInstruction/MDefinition state like flags and
  // resume points.
  virtual bool canClone() const { return false; }
  virtual MInstruction* clone(TempAllocator& alloc,
                              const MDefinitionVector& inputs) const {
    MOZ_CRASH();
  }

  // Instructions needing to hook into type analysis should return a
  // TypePolicy.
  virtual const TypePolicy* typePolicy() = 0;
  virtual MIRType typePolicySpecialization() = 0;
};

// Note: GenerateOpcodeFiles.py generates MOpcodesGenerated.h based on the
// INSTRUCTION_HEADER* macros.
#define INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(opcode) \
  static const Opcode classOpcode = Opcode::opcode;   \
  using MThisOpcode = M##opcode;

#define INSTRUCTION_HEADER(opcode)                 \
  INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(opcode)    \
  virtual const TypePolicy* typePolicy() override; \
  virtual MIRType typePolicySpecialization() override;

#define ALLOW_CLONE(typename)                                                \
  bool canClone() const override { return true; }                            \
  MInstruction* clone(TempAllocator& alloc, const MDefinitionVector& inputs) \
      const override {                                                       \
    MInstruction* res = new (alloc) typename(*this);                         \
    for (size_t i = 0; i < numOperands(); i++)                               \
      res->replaceOperand(i, inputs[i]);                                     \
    return res;                                                              \
  }

// Adds MFoo::New functions which are mirroring the arguments of the
// constructors. Opcodes which are using this macro can be called with a
// TempAllocator, or the fallible version of the TempAllocator.
#define TRIVIAL_NEW_WRAPPERS                                               \
  template <typename... Args>                                              \
  static MThisOpcode* New(TempAllocator& alloc, Args&&... args) {          \
    return new (alloc) MThisOpcode(std::forward<Args>(args)...);           \
  }                                                                        \
  template <typename... Args>                                              \
  static MThisOpcode* New(TempAllocator::Fallible alloc, Args&&... args) { \
    return new (alloc) MThisOpcode(std::forward<Args>(args)...);           \
  }

// These macros are used as a syntactic sugar for writting getOperand
// accessors. They are meant to be used in the body of MIR Instructions as
// follows:
//
//   public:
//     INSTRUCTION_HEADER(Foo)
//     NAMED_OPERANDS((0, lhs), (1, rhs))
//
// The above example defines 2 accessors, one named "lhs" accessing the first
// operand, and a one named "rhs" accessing the second operand.
#define NAMED_OPERAND_ACCESSOR(Index, Name) \
  MDefinition* Name() const { return getOperand(Index); }
#define NAMED_OPERAND_ACCESSOR_APPLY(Args) NAMED_OPERAND_ACCESSOR Args
#define NAMED_OPERANDS(...) \
  MOZ_FOR_EACH(NAMED_OPERAND_ACCESSOR_APPLY, (), (__VA_ARGS__))

template <size_t Arity>
class MAryInstruction : public MInstruction {
  mozilla::Array<MUse, Arity> operands_;

 protected:
  MUse* getUseFor(size_t index) final { return &operands_[index]; }
  const MUse* getUseFor(size_t index) const final { return &operands_[index]; }
  void initOperand(size_t index, MDefinition* operand) {
    operands_[index].init(operand, this);
  }

 public:
  MDefinition* getOperand(size_t index) const final {
    return operands_[index].producer();
  }
  size_t numOperands() const final { return Arity; }
#ifdef DEBUG
  static const size_t staticNumOperands = Arity;
#endif
  size_t indexOf(const MUse* u) const final {
    MOZ_ASSERT(u >= &operands_[0]);
    MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
    return u - &operands_[0];
  }
  void replaceOperand(size_t index, MDefinition* operand) final {
    operands_[index].replaceProducer(operand);
  }

  explicit MAryInstruction(Opcode op) : MInstruction(op) {}

  explicit MAryInstruction(const MAryInstruction<Arity>& other)
      : MInstruction(other) {
    for (int i = 0; i < (int)Arity;
         i++) {  // N.B. use |int| to avoid warnings when Arity == 0
      operands_[i].init(other.operands_[i].producer(), this);
    }
  }
};

class MNullaryInstruction : public MAryInstruction<0>,
                            public NoTypePolicy::Data {
 protected:
  explicit MNullaryInstruction(Opcode op) : MAryInstruction(op) {}

  HashNumber valueHash() const override;
};

class MUnaryInstruction : public MAryInstruction<1> {
 protected:
  MUnaryInstruction(Opcode op, MDefinition* ins) : MAryInstruction(op) {
    initOperand(0, ins);
  }

  HashNumber valueHash() const override;

 public:
  NAMED_OPERANDS((0, input))
};

class MBinaryInstruction : public MAryInstruction<2> {
 protected:
  MBinaryInstruction(Opcode op, MDefinition* left, MDefinition* right)
      : MAryInstruction(op) {
    initOperand(0, left);
    initOperand(1, right);
  }

 public:
  NAMED_OPERANDS((0, lhs), (1, rhs))

 protected:
  HashNumber valueHash() const override;

  bool binaryCongruentTo(const MDefinition* ins) const {
    if (op() != ins->op()) {
      return false;
    }

    if (type() != ins->type()) {
      return false;
    }

    if (isEffectful() || ins->isEffectful()) {
      return false;
    }

    const MDefinition* left = getOperand(0);
    const MDefinition* right = getOperand(1);
    if (isCommutative() && left->id() > right->id()) {
      std::swap(left, right);
    }

    const MBinaryInstruction* bi = static_cast<const MBinaryInstruction*>(ins);
    const MDefinition* insLeft = bi->getOperand(0);
    const MDefinition* insRight = bi->getOperand(1);
    if (bi->isCommutative() && insLeft->id() > insRight->id()) {
      std::swap(insLeft, insRight);
    }

    return left == insLeft && right == insRight;
  }

 public:
  // Return if the operands to this instruction are both unsigned.
  static bool unsignedOperands(MDefinition* left, MDefinition* right);
  bool unsignedOperands();

  // Replace any wrapping operands with the underlying int32 operands
  // in case of unsigned operands.
  void replaceWithUnsignedOperands();
};

class MTernaryInstruction : public MAryInstruction<3> {
 protected:
  MTernaryInstruction(Opcode op, MDefinition* first, MDefinition* second,
                      MDefinition* third)
      : MAryInstruction(op) {
    initOperand(0, first);
    initOperand(1, second);
    initOperand(2, third);
  }

  HashNumber valueHash() const override;
};

class MQuaternaryInstruction : public MAryInstruction<4> {
 protected:
  MQuaternaryInstruction(Opcode op, MDefinition* first, MDefinition* second,
                         MDefinition* third, MDefinition* fourth)
      : MAryInstruction(op) {
    initOperand(0, first);
    initOperand(1, second);
    initOperand(2, third);
    initOperand(3, fourth);
  }

  HashNumber valueHash() const override;
};

template <class T>
class MVariadicT : public T {
  FixedList<MUse> operands_;

 protected:
  explicit MVariadicT(typename T::Opcode op) : T(op) {}
  [[nodiscard]] bool init(TempAllocator& alloc, size_t length) {
    return operands_.init(alloc, length);
  }
  void initOperand(size_t index, MDefinition* operand) {
    // FixedList doesn't initialize its elements, so do an unchecked init.
    operands_[index].initUnchecked(operand, this);
  }
  MUse* getUseFor(size_t index) final { return &operands_[index]; }
  const MUse* getUseFor(size_t index) const final { return &operands_[index]; }

  // The MWasmCallBase mixin performs initialization for it's subclasses.
  friend class MWasmCallBase;

 public:
  // Will assert if called before initialization.
  MDefinition* getOperand(size_t index) const final {
    return operands_[index].producer();
  }
  size_t numOperands() const final { return operands_.length(); }
  size_t indexOf(const MUse* u) const final {
    MOZ_ASSERT(u >= &operands_[0]);
    MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
    return u - &operands_[0];
  }
  void replaceOperand(size_t index, MDefinition* operand) final {
    operands_[index].replaceProducer(operand);
  }
};

// An instruction with a variable number of operands. Note that the
// MFoo::New constructor for variadic instructions fallibly
// initializes the operands_ array and must be checked for OOM.
using MVariadicInstruction = MVariadicT<MInstruction>;

MIR_OPCODE_CLASS_GENERATED

// Truncation barrier. This is intended for protecting its input against
// follow-up truncation optimizations.
class MLimitedTruncate : public MUnaryInstruction,
                         public ConvertToInt32Policy<0>::Data {
  TruncateKind truncate_;
  TruncateKind truncateLimit_;

  MLimitedTruncate(MDefinition* input, TruncateKind limit)
      : MUnaryInstruction(classOpcode, input),
        truncate_(TruncateKind::NoTruncate),
        truncateLimit_(limit) {
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(LimitedTruncate)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  TruncateKind operandTruncateKind(size_t index) const override;
  TruncateKind truncateKind() const { return truncate_; }
  void setTruncateKind(TruncateKind kind) { truncate_ = kind; }
};

// A constant js::Value.
class MConstant : public MNullaryInstruction {
  struct Payload {
    union {
      bool b;
      int32_t i32;
      int64_t i64;
      intptr_t iptr;
      float f;
      double d;
      JSString* str;
      JS::Symbol* sym;
      BigInt* bi;
      JSObject* obj;
      Shape* shape;
      uint64_t asBits;
    };
    Payload() : asBits(0) {}
  };

  Payload payload_;

  static_assert(sizeof(Payload) == sizeof(uint64_t),
                "asBits must be big enough for all payload bits");

#ifdef DEBUG
  void assertInitializedPayload() const;
#else
  void assertInitializedPayload() const {}
#endif

  MConstant(TempAllocator& alloc, const Value& v);
  explicit MConstant(JSObject* obj);
  explicit MConstant(Shape* shape);
  explicit MConstant(float f);
  explicit MConstant(MIRType type, int64_t i);

 public:
  INSTRUCTION_HEADER(Constant)
  static MConstant* New(TempAllocator& alloc, const Value& v);
  static MConstant* New(TempAllocator::Fallible alloc, const Value& v);
  static MConstant* New(TempAllocator& alloc, const Value& v, MIRType type);
  static MConstant* NewFloat32(TempAllocator& alloc, double d);
  static MConstant* NewInt64(TempAllocator& alloc, int64_t i);
  static MConstant* NewIntPtr(TempAllocator& alloc, intptr_t i);
  static MConstant* NewObject(TempAllocator& alloc, JSObject* v);
  static MConstant* NewShape(TempAllocator& alloc, Shape* s);
  static MConstant* Copy(TempAllocator& alloc, MConstant* src) {
    return new (alloc) MConstant(*src);
  }

  // Try to convert this constant to boolean, similar to js::ToBoolean.
  // Returns false if the type is MIRType::Magic* or MIRType::Object.
  [[nodiscard]] bool valueToBoolean(bool* res) const;

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

  HashNumber valueHash() const override;
  bool congruentTo(const MDefinition* ins) const override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;

  bool canProduceFloat32() const override;

  ALLOW_CLONE(MConstant)

  bool equals(const MConstant* other) const {
    assertInitializedPayload();
    return type() == other->type() && payload_.asBits == other->payload_.asBits;
  }

  bool toBoolean() const {
    MOZ_ASSERT(type() == MIRType::Boolean);
    return payload_.b;
  }
  int32_t toInt32() const {
    MOZ_ASSERT(type() == MIRType::Int32);
    return payload_.i32;
  }
  int64_t toInt64() const {
    MOZ_ASSERT(type() == MIRType::Int64);
    return payload_.i64;
  }
  intptr_t toIntPtr() const {
    MOZ_ASSERT(type() == MIRType::IntPtr);
    return payload_.iptr;
  }
  bool isInt32(int32_t i) const {
    return type() == MIRType::Int32 && payload_.i32 == i;
  }
  bool isInt64(int64_t i) const {
    return type() == MIRType::Int64 && payload_.i64 == i;
  }
  const double& toDouble() const {
    MOZ_ASSERT(type() == MIRType::Double);
    return payload_.d;
  }
  const float& toFloat32() const {
    MOZ_ASSERT(type() == MIRType::Float32);
    return payload_.f;
  }
  JSString* toString() const {
    MOZ_ASSERT(type() == MIRType::String);
    return payload_.str;
  }
  JS::Symbol* toSymbol() const {
    MOZ_ASSERT(type() == MIRType::Symbol);
    return payload_.sym;
  }
  BigInt* toBigInt() const {
    MOZ_ASSERT(type() == MIRType::BigInt);
    return payload_.bi;
  }
  JSObject& toObject() const {
    MOZ_ASSERT(type() == MIRType::Object);
    return *payload_.obj;
  }
  JSObject* toObjectOrNull() const {
    if (type() == MIRType::Object) {
      return payload_.obj;
    }
    MOZ_ASSERT(type() == MIRType::Null);
    return nullptr;
  }
  Shape* toShape() const {
    MOZ_ASSERT(type() == MIRType::Shape);
    return payload_.shape;
  }

  bool isTypeRepresentableAsDouble() const {
    return IsTypeRepresentableAsDouble(type());
  }
  double numberToDouble() const {
    MOZ_ASSERT(isTypeRepresentableAsDouble());
    if (type() == MIRType::Int32) {
      return toInt32();
    }
    if (type() == MIRType::Double) {
      return toDouble();
    }
    return toFloat32();
  }

  // Convert this constant to a js::Value. Float32 constants will be stored
  // as DoubleValue and NaNs are canonicalized. Callers must be careful: not
  // all constants can be represented by js::Value (wasm supports int64).
  Value toJSValue() const;
};

class MWasmNullConstant : public MNullaryInstruction {
  explicit MWasmNullConstant() : MNullaryInstruction(classOpcode) {
    setResultType(MIRType::RefOrNull);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmNullConstant)
  TRIVIAL_NEW_WRAPPERS

  HashNumber valueHash() const override;
  bool congruentTo(const MDefinition* ins) const override {
    return ins->isWasmNullConstant();
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MWasmNullConstant)
};

// Floating-point value as created by wasm. Just a constant value, used to
// effectively inhibit all the MIR optimizations. This uses the same LIR nodes
// as a MConstant of the same type would.
class MWasmFloatConstant : public MNullaryInstruction {
  union {
    float f32_;
    double f64_;
#ifdef ENABLE_WASM_SIMD
    int8_t s128_[16];
    uint64_t bits_[2];
#else
    uint64_t bits_[1];
#endif
  } u;

  explicit MWasmFloatConstant(MIRType type) : MNullaryInstruction(classOpcode) {
    u.bits_[0] = 0;
#ifdef ENABLE_WASM_SIMD
    u.bits_[1] = 0;
#endif
    setResultType(type);
  }

 public:
  INSTRUCTION_HEADER(WasmFloatConstant)

  static MWasmFloatConstant* NewDouble(TempAllocator& alloc, double d) {
    auto* ret = new (alloc) MWasmFloatConstant(MIRType::Double);
    ret->u.f64_ = d;
    return ret;
  }

  static MWasmFloatConstant* NewFloat32(TempAllocator& alloc, float f) {
    auto* ret = new (alloc) MWasmFloatConstant(MIRType::Float32);
    ret->u.f32_ = f;
    return ret;
  }

#ifdef ENABLE_WASM_SIMD
  static MWasmFloatConstant* NewSimd128(TempAllocator& alloc,
                                        const SimdConstant& s) {
    auto* ret = new (alloc) MWasmFloatConstant(MIRType::Simd128);
    memcpy(ret->u.s128_, s.bytes(), 16);
    return ret;
  }
#endif

  HashNumber valueHash() const override;
  bool congruentTo(const MDefinition* ins) const override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  const double& toDouble() const {
    MOZ_ASSERT(type() == MIRType::Double);
    return u.f64_;
  }
  const float& toFloat32() const {
    MOZ_ASSERT(type() == MIRType::Float32);
    return u.f32_;
  }
#ifdef ENABLE_WASM_SIMD
  const SimdConstant toSimd128() const {
    MOZ_ASSERT(type() == MIRType::Simd128);
    return SimdConstant::CreateX16(u.s128_);
  }
#endif
#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    switch (type()) {
      case MIRType::Float32:
        SprintfLiteral(buf, "f32{%e}", (double)u.f32_);
        break;
      case MIRType::Double:
        SprintfLiteral(buf, "f64{%e}", u.f64_);
        break;
#  ifdef ENABLE_WASM_SIMD
      case MIRType::Simd128:
        SprintfLiteral(buf, "v128{[1]=%016llx:[0]=%016llx}",
                       (unsigned long long int)u.bits_[1],
                       (unsigned long long int)u.bits_[0]);
        break;
#  endif
      default:
        SprintfLiteral(buf, "!!getExtras: missing case!!");
        break;
    }
    extras->add(buf);
  }
#endif
};

class MParameter : public MNullaryInstruction {
  int32_t index_;

  explicit MParameter(int32_t index)
      : MNullaryInstruction(classOpcode), index_(index) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(Parameter)
  TRIVIAL_NEW_WRAPPERS

  static const int32_t THIS_SLOT = -1;
  int32_t index() const { return index_; }
#ifdef JS_JITSPEW
  void printOpcode(GenericPrinter& out) const override;
#endif
  HashNumber valueHash() const override;
  bool congruentTo(const MDefinition* ins) const override;
};

class MControlInstruction : public MInstruction {
 protected:
  explicit MControlInstruction(Opcode op) : MInstruction(op) {}

 public:
  virtual size_t numSuccessors() const = 0;
  virtual MBasicBlock* getSuccessor(size_t i) const = 0;
  virtual void replaceSuccessor(size_t i, MBasicBlock* successor) = 0;

  void initSuccessor(size_t i, MBasicBlock* successor) {
    MOZ_ASSERT(!getSuccessor(i));
    replaceSuccessor(i, successor);
  }

  bool isControlInstruction() const override { return true; }

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

class MTableSwitch final : public MControlInstruction,
                           public NoFloatPolicy<0>::Data {
  // The successors of the tableswitch
  // - First successor = the default case
  // - Successors 2 and higher = the cases
  Vector<MBasicBlock*, 0, JitAllocPolicy> successors_;
  // Index into successors_ sorted on case index
  Vector<size_t, 0, JitAllocPolicy> cases_;

  MUse operand_;
  int32_t low_;
  int32_t high_;

  void initOperand(size_t index, MDefinition* operand) {
    MOZ_ASSERT(index == 0);
    operand_.init(operand, this);
  }

  MTableSwitch(TempAllocator& alloc, MDefinition* ins, int32_t low,
               int32_t high)
      : MControlInstruction(classOpcode),
        successors_(alloc),
        cases_(alloc),
        low_(low),
        high_(high) {
    initOperand(0, ins);
  }

 protected:
  MUse* getUseFor(size_t index) override {
    MOZ_ASSERT(index == 0);
    return &operand_;
  }

  const MUse* getUseFor(size_t index) const override {
    MOZ_ASSERT(index == 0);
    return &operand_;
  }

 public:
  INSTRUCTION_HEADER(TableSwitch)

  static MTableSwitch* New(TempAllocator& alloc, MDefinition* ins, int32_t low,
                           int32_t high) {
    return new (alloc) MTableSwitch(alloc, ins, low, high);
  }

  size_t numSuccessors() const override { return successors_.length(); }

  [[nodiscard]] bool addSuccessor(MBasicBlock* successor, size_t* index) {
    MOZ_ASSERT(successors_.length() < (size_t)(high_ - low_ + 2));
    MOZ_ASSERT(!successors_.empty());
    *index = successors_.length();
    return successors_.append(successor);
  }

  MBasicBlock* getSuccessor(size_t i) const override {
    MOZ_ASSERT(i < numSuccessors());
    return successors_[i];
  }

  void replaceSuccessor(size_t i, MBasicBlock* successor) override {
    MOZ_ASSERT(i < numSuccessors());
    successors_[i] = successor;
  }

  int32_t low() const { return low_; }

  int32_t high() const { return high_; }

  MBasicBlock* getDefault() const { return getSuccessor(0); }

  MBasicBlock* getCase(size_t i) const { return getSuccessor(cases_[i]); }

  [[nodiscard]] bool addDefault(MBasicBlock* block, size_t* index = nullptr) {
    MOZ_ASSERT(successors_.empty());
    if (index) {
      *index = 0;
    }
    return successors_.append(block);
  }

  [[nodiscard]] bool addCase(size_t successorIndex) {
    return cases_.append(successorIndex);
  }

  size_t numCases() const { return high() - low() + 1; }

  MDefinition* getOperand(size_t index) const override {
    MOZ_ASSERT(index == 0);
    return operand_.producer();
  }

  size_t numOperands() const override { return 1; }

  size_t indexOf(const MUse* u) const final {
    MOZ_ASSERT(u == getUseFor(0));
    return 0;
  }

  void replaceOperand(size_t index, MDefinition* operand) final {
    MOZ_ASSERT(index == 0);
    operand_.replaceProducer(operand);
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

template <size_t Arity, size_t Successors>
class MAryControlInstruction : public MControlInstruction {
  mozilla::Array<MUse, Arity> operands_;
  mozilla::Array<MBasicBlock*, Successors> successors_;

 protected:
  explicit MAryControlInstruction(Opcode op) : MControlInstruction(op) {}
  void setSuccessor(size_t index, MBasicBlock* successor) {
    successors_[index] = successor;
  }

  MUse* getUseFor(size_t index) final { return &operands_[index]; }
  const MUse* getUseFor(size_t index) const final { return &operands_[index]; }
  void initOperand(size_t index, MDefinition* operand) {
    operands_[index].init(operand, this);
  }

 public:
  MDefinition* getOperand(size_t index) const final {
    return operands_[index].producer();
  }
  size_t numOperands() const final { return Arity; }
  size_t indexOf(const MUse* u) const final {
    MOZ_ASSERT(u >= &operands_[0]);
    MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
    return u - &operands_[0];
  }
  void replaceOperand(size_t index, MDefinition* operand) final {
    operands_[index].replaceProducer(operand);
  }
  size_t numSuccessors() const final { return Successors; }
  MBasicBlock* getSuccessor(size_t i) const final { return successors_[i]; }
  void replaceSuccessor(size_t i, MBasicBlock* succ) final {
    successors_[i] = succ;
  }
};

template <size_t Successors>
class MVariadicControlInstruction : public MVariadicT<MControlInstruction> {
  mozilla::Array<MBasicBlock*, Successors> successors_;

 protected:
  explicit MVariadicControlInstruction(Opcode op)
      : MVariadicT<MControlInstruction>(op) {}
  void setSuccessor(size_t index, MBasicBlock* successor) {
    successors_[index] = successor;
  }

 public:
  size_t numSuccessors() const final { return Successors; }
  MBasicBlock* getSuccessor(size_t i) const final { return successors_[i]; }
  void replaceSuccessor(size_t i, MBasicBlock* succ) final {
    successors_[i] = succ;
  }
};

// Jump to the start of another basic block.
class MGoto : public MAryControlInstruction<0, 1>, public NoTypePolicy::Data {
  explicit MGoto(MBasicBlock* target) : MAryControlInstruction(classOpcode) {
    setSuccessor(TargetIndex, target);
  }

 public:
  INSTRUCTION_HEADER(Goto)
  static MGoto* New(TempAllocator& alloc, MBasicBlock* target);
  static MGoto* New(TempAllocator::Fallible alloc, MBasicBlock* target);

  // Variant that may patch the target later.
  static MGoto* New(TempAllocator& alloc);

  static constexpr size_t TargetIndex = 0;

  MBasicBlock* target() { return getSuccessor(TargetIndex); }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "Block%u", GetMBasicBlockId(target()));
    extras->add(buf);
  }
#endif
};

// Tests if the input instruction evaluates to true or false, and jumps to the
// start of a corresponding basic block.
class MTest : public MAryControlInstruction<1, 2>, public TestPolicy::Data {
  // It is allowable to specify `trueBranch` or `falseBranch` as nullptr and
  // patch it in later.
  MTest(MDefinition* ins, MBasicBlock* trueBranch, MBasicBlock* falseBranch)
      : MAryControlInstruction(classOpcode) {
    initOperand(0, ins);
    setSuccessor(TrueBranchIndex, trueBranch);
    setSuccessor(FalseBranchIndex, falseBranch);
  }

  TypeDataList observedTypes_;

 public:
  INSTRUCTION_HEADER(Test)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, input))

  const TypeDataList& observedTypes() const { return observedTypes_; }
  void setObservedTypes(const TypeDataList& observed) {
    observedTypes_ = observed;
  }

  static constexpr size_t TrueBranchIndex = 0;
  static constexpr size_t FalseBranchIndex = 1;

  MBasicBlock* ifTrue() const { return getSuccessor(TrueBranchIndex); }
  MBasicBlock* ifFalse() const { return getSuccessor(FalseBranchIndex); }
  MBasicBlock* branchSuccessor(BranchDirection dir) const {
    return (dir == TRUE_BRANCH) ? ifTrue() : ifFalse();
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsDoubleNegation(TempAllocator& alloc);
  MDefinition* foldsConstant(TempAllocator& alloc);
  MDefinition* foldsTypes(TempAllocator& alloc);
  MDefinition* foldsNeedlessControlFlow(TempAllocator& alloc);
  MDefinition* foldsTo(TempAllocator& alloc) override;

#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "true->Block%u false->Block%u",
                   GetMBasicBlockId(ifTrue()), GetMBasicBlockId(ifFalse()));
    extras->add(buf);
  }
#endif
};

// Returns from this function to the previous caller.
class MReturn : public MAryControlInstruction<1, 0>,
                public BoxInputsPolicy::Data {
  explicit MReturn(MDefinition* ins) : MAryControlInstruction(classOpcode) {
    initOperand(0, ins);
  }

 public:
  INSTRUCTION_HEADER(Return)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, input))

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MNewArray : public MUnaryInstruction, public NoTypePolicy::Data {
 private:
  // Number of elements to allocate for the array.
  uint32_t length_;

  // Heap where the array should be allocated.
  gc::Heap initialHeap_;

  bool vmCall_;

  MNewArray(uint32_t length, MConstant* templateConst, gc::Heap initialHeap,
            bool vmCall = false);

 public:
  INSTRUCTION_HEADER(NewArray)
  TRIVIAL_NEW_WRAPPERS

  static MNewArray* NewVM(TempAllocator& alloc, uint32_t length,
                          MConstant* templateConst, gc::Heap initialHeap) {
    return new (alloc) MNewArray(length, templateConst, initialHeap, true);
  }

  uint32_t length() const { return length_; }

  JSObject* templateObject() const {
    return getOperand(0)->toConstant()->toObjectOrNull();
  }

  gc::Heap initialHeap() const { return initialHeap_; }

  bool isVMCall() const { return vmCall_; }

  // NewArray is marked as non-effectful because all our allocations are
  // either lazy when we are using "new Array(length)" or bounded by the
  // script or the stack size when we are using "new Array(...)" or "[...]"
  // notations.  So we might have to allocate the array twice if we bail
  // during the computation of the first element of the square braket
  // notation.
  virtual AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override {
    // The template object can safely be used in the recover instruction
    // because it can never be mutated by any other function execution.
    return templateObject() != nullptr;
  }
};

class MNewTypedArray : public MUnaryInstruction, public NoTypePolicy::Data {
  gc::Heap initialHeap_;

  MNewTypedArray(MConstant* templateConst, gc::Heap initialHeap)
      : MUnaryInstruction(classOpcode, templateConst),
        initialHeap_(initialHeap) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(NewTypedArray)
  TRIVIAL_NEW_WRAPPERS

  TypedArrayObject* templateObject() const {
    return &getOperand(0)->toConstant()->toObject().as<TypedArrayObject>();
  }

  gc::Heap initialHeap() const { return initialHeap_; }

  virtual AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MNewObject : public MUnaryInstruction, public NoTypePolicy::Data {
 public:
  enum Mode { ObjectLiteral, ObjectCreate };

 private:
  gc::Heap initialHeap_;
  Mode mode_;
  bool vmCall_;

  MNewObject(MConstant* templateConst, gc::Heap initialHeap, Mode mode,
             bool vmCall = false)
      : MUnaryInstruction(classOpcode, templateConst),
        initialHeap_(initialHeap),
        mode_(mode),
        vmCall_(vmCall) {
    if (mode == ObjectLiteral) {
      MOZ_ASSERT(!templateObject());
    } else {
      MOZ_ASSERT(templateObject());
    }
    setResultType(MIRType::Object);

    // The constant is kept separated in a MConstant, this way we can safely
    // mark it during GC if we recover the object allocation.  Otherwise, by
    // making it emittedAtUses, we do not produce register allocations for
    // it and inline its content inside the code produced by the
    // CodeGenerator.
    if (templateConst->toConstant()->type() == MIRType::Object) {
      templateConst->setEmittedAtUses();
    }
  }

 public:
  INSTRUCTION_HEADER(NewObject)
  TRIVIAL_NEW_WRAPPERS

  static MNewObject* NewVM(TempAllocator& alloc, MConstant* templateConst,
                           gc::Heap initialHeap, Mode mode) {
    return new (alloc) MNewObject(templateConst, initialHeap, mode, true);
  }

  Mode mode() const { return mode_; }

  JSObject* templateObject() const {
    return getOperand(0)->toConstant()->toObjectOrNull();
  }

  gc::Heap initialHeap() const { return initialHeap_; }

  bool isVMCall() const { return vmCall_; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override {
    // The template object can safely be used in the recover instruction
    // because it can never be mutated by any other function execution.
    return templateObject() != nullptr;
  }
};

class MNewPlainObject : public MUnaryInstruction, public NoTypePolicy::Data {
 private:
  uint32_t numFixedSlots_;
  uint32_t numDynamicSlots_;
  gc::AllocKind allocKind_;
  gc::Heap initialHeap_;

  MNewPlainObject(MConstant* shapeConst, uint32_t numFixedSlots,
                  uint32_t numDynamicSlots, gc::AllocKind allocKind,
                  gc::Heap initialHeap)
      : MUnaryInstruction(classOpcode, shapeConst),
        numFixedSlots_(numFixedSlots),
        numDynamicSlots_(numDynamicSlots),
        allocKind_(allocKind),
        initialHeap_(initialHeap) {
    setResultType(MIRType::Object);

    // The shape constant is kept separated in a MConstant. This way we can
    // safely mark it during GC if we recover the object allocation. Otherwise,
    // by making it emittedAtUses, we do not produce register allocations for it
    // and inline its content inside the code produced by the CodeGenerator.
    MOZ_ASSERT(shapeConst->toConstant()->type() == MIRType::Shape);
    shapeConst->setEmittedAtUses();
  }

 public:
  INSTRUCTION_HEADER(NewPlainObject)
  TRIVIAL_NEW_WRAPPERS

  const Shape* shape() const { return getOperand(0)->toConstant()->toShape(); }

  uint32_t numFixedSlots() const { return numFixedSlots_; }
  uint32_t numDynamicSlots() const { return numDynamicSlots_; }
  gc::AllocKind allocKind() const { return allocKind_; }
  gc::Heap initialHeap() const { return initialHeap_; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MNewArrayObject : public MUnaryInstruction, public NoTypePolicy::Data {
 private:
  uint32_t length_;
  gc::Heap initialHeap_;

  MNewArrayObject(TempAllocator& alloc, MConstant* shapeConst, uint32_t length,
                  gc::Heap initialHeap)
      : MUnaryInstruction(classOpcode, shapeConst),
        length_(length),
        initialHeap_(initialHeap) {
    setResultType(MIRType::Object);
    MOZ_ASSERT(shapeConst->toConstant()->type() == MIRType::Shape);
    shapeConst->setEmittedAtUses();
  }

 public:
  INSTRUCTION_HEADER(NewArrayObject)
  TRIVIAL_NEW_WRAPPERS

  static MNewArrayObject* New(TempAllocator& alloc, MConstant* shapeConst,
                              uint32_t length, gc::Heap initialHeap) {
    return new (alloc) MNewArrayObject(alloc, shapeConst, length, initialHeap);
  }

  const Shape* shape() const { return getOperand(0)->toConstant()->toShape(); }

  // See MNewArray::getAliasSet comment.
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  uint32_t length() const { return length_; }
  gc::Heap initialHeap() const { return initialHeap_; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MNewIterator : public MUnaryInstruction, public NoTypePolicy::Data {
 public:
  enum Type {
    ArrayIterator,
    StringIterator,
    RegExpStringIterator,
  };

 private:
  Type type_;

  MNewIterator(MConstant* templateConst, Type type)
      : MUnaryInstruction(classOpcode, templateConst), type_(type) {
    setResultType(MIRType::Object);
    templateConst->setEmittedAtUses();
  }

 public:
  INSTRUCTION_HEADER(NewIterator)
  TRIVIAL_NEW_WRAPPERS

  Type type() const { return type_; }

  JSObject* templateObject() {
    return getOperand(0)->toConstant()->toObjectOrNull();
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

// Represent the content of all slots of an object.  This instruction is not
// lowered and is not used to generate code.
class MObjectState : public MVariadicInstruction,
                     public NoFloatPolicyAfter<1>::Data {
 private:
  uint32_t numSlots_;
  uint32_t numFixedSlots_;

  explicit MObjectState(JSObject* templateObject);
  explicit MObjectState(const Shape* shape);
  explicit MObjectState(MObjectState* state);

  [[nodiscard]] bool init(TempAllocator& alloc, MDefinition* obj);

  void initSlot(uint32_t slot, MDefinition* def) { initOperand(slot + 1, def); }

 public:
  INSTRUCTION_HEADER(ObjectState)
  NAMED_OPERANDS((0, object))

  // Return the template object of any object creation which can be recovered
  // on bailout.
  static JSObject* templateObjectOf(MDefinition* obj);

  static MObjectState* New(TempAllocator& alloc, MDefinition* obj);
  static MObjectState* Copy(TempAllocator& alloc, MObjectState* state);

  // As we might do read of uninitialized properties, we have to copy the
  // initial values from the template object.
  void initFromTemplateObject(TempAllocator& alloc, MDefinition* undefinedVal);

  size_t numFixedSlots() const { return numFixedSlots_; }
  size_t numSlots() const { return numSlots_; }

  MDefinition* getSlot(uint32_t slot) const { return getOperand(slot + 1); }
  void setSlot(uint32_t slot, MDefinition* def) {
    replaceOperand(slot + 1, def);
  }

  bool hasFixedSlot(uint32_t slot) const {
    return slot < numSlots() && slot < numFixedSlots();
  }
  MDefinition* getFixedSlot(uint32_t slot) const {
    MOZ_ASSERT(slot < numFixedSlots());
    return getSlot(slot);
  }
  void setFixedSlot(uint32_t slot, MDefinition* def) {
    MOZ_ASSERT(slot < numFixedSlots());
    setSlot(slot, def);
  }

  bool hasDynamicSlot(uint32_t slot) const {
    return numFixedSlots() < numSlots() && slot < numSlots() - numFixedSlots();
  }
  MDefinition* getDynamicSlot(uint32_t slot) const {
    return getSlot(slot + numFixedSlots());
  }
  void setDynamicSlot(uint32_t slot, MDefinition* def) {
    setSlot(slot + numFixedSlots(), def);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

// Represent the contents of all elements of an array.  This instruction is not
// lowered and is not used to generate code.
class MArrayState : public MVariadicInstruction,
                    public NoFloatPolicyAfter<2>::Data {
 private:
  uint32_t numElements_;

  explicit MArrayState(MDefinition* arr);

  [[nodiscard]] bool init(TempAllocator& alloc, MDefinition* obj,
                          MDefinition* len);

  void initElement(uint32_t index, MDefinition* def) {
    initOperand(index + 2, def);
  }

 public:
  INSTRUCTION_HEADER(ArrayState)
  NAMED_OPERANDS((0, array), (1, initializedLength))

  static MArrayState* New(TempAllocator& alloc, MDefinition* arr,
                          MDefinition* initLength);
  static MArrayState* Copy(TempAllocator& alloc, MArrayState* state);

  void initFromTemplateObject(TempAllocator& alloc, MDefinition* undefinedVal);

  void setInitializedLength(MDefinition* def) { replaceOperand(1, def); }

  size_t numElements() const { return numElements_; }

  MDefinition* getElement(uint32_t index) const {
    return getOperand(index + 2);
  }
  void setElement(uint32_t index, MDefinition* def) {
    replaceOperand(index + 2, def);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

// WrappedFunction stores information about a function that can safely be used
// off-thread. In particular, a function's flags can be modified on the main
// thread as functions are relazified and delazified, so we must be careful not
// to access these flags off-thread.
class WrappedFunction : public TempObject {
  // If this is a native function without a JitEntry, the JSFunction*.
  CompilerFunction nativeFun_;
  uint16_t nargs_;
  js::FunctionFlags flags_;

 public:
  WrappedFunction(JSFunction* nativeFun, uint16_t nargs, FunctionFlags flags);

  // Note: When adding new accessors be sure to add consistency asserts
  // to the constructor.

  size_t nargs() const { return nargs_; }

  bool isNativeWithoutJitEntry() const {
    return flags_.isNativeWithoutJitEntry();
  }
  bool hasJitEntry() const { return flags_.hasJitEntry(); }
  bool isConstructor() const { return flags_.isConstructor(); }
  bool isClassConstructor() const { return flags_.isClassConstructor(); }

  // These fields never change, they can be accessed off-main thread.
  JSNative native() const {
    MOZ_ASSERT(isNativeWithoutJitEntry());
    return nativeFun_->nativeUnchecked();
  }
  bool hasJitInfo() const {
    return flags_.isBuiltinNative() && nativeFun_->jitInfoUnchecked();
  }
  const JSJitInfo* jitInfo() const {
    MOZ_ASSERT(hasJitInfo());
    return nativeFun_->jitInfoUnchecked();
  }

  JSFunction* rawNativeJSFunction() const { return nativeFun_; }
};

enum class DOMObjectKind : uint8_t { Proxy, Native };

class MCallBase : public MVariadicInstruction, public CallPolicy::Data {
 protected:
  // The callee, this, and the actual arguments are all operands of MCall.
  static const size_t CalleeOperandIndex = 0;
  static const size_t NumNonArgumentOperands = 1;

  explicit MCallBase(Opcode op) : MVariadicInstruction(op) {}

 public:
  void initCallee(MDefinition* func) { initOperand(CalleeOperandIndex, func); }
  MDefinition* getCallee() const { return getOperand(CalleeOperandIndex); }

  void replaceCallee(MInstruction* newfunc) {
    replaceOperand(CalleeOperandIndex, newfunc);
  }

  void addArg(size_t argnum, MDefinition* arg);

  MDefinition* getArg(uint32_t index) const {
    return getOperand(NumNonArgumentOperands + index);
  }

  // The number of stack arguments is the max between the number of formal
  // arguments and the number of actual arguments. The number of stack
  // argument includes the |undefined| padding added in case of underflow.
  // Includes |this|.
  uint32_t numStackArgs() const {
    return numOperands() - NumNonArgumentOperands;
  }
  uint32_t paddedNumStackArgs() const {
    if (JitStackValueAlignment > 1) {
      return AlignBytes(numStackArgs(), JitStackValueAlignment);
    }
    return numStackArgs();
  }

  static size_t IndexOfThis() { return NumNonArgumentOperands; }
  static size_t IndexOfArgument(size_t index) {
    return NumNonArgumentOperands + index + 1;  // +1 to skip |this|.
  }
  static size_t IndexOfStackArg(size_t index) {
    return NumNonArgumentOperands + index;
  }
};

class MCall : public MCallBase {
 protected:
  // Monomorphic cache for MCalls with a single JSFunction target.
  WrappedFunction* target_;

  // Original value of argc from the bytecode.
  uint32_t numActualArgs_;

  // True if the call is for JSOp::New or JSOp::SuperCall.
  bool construct_ : 1;

  // True if the caller does not use the return value.
  bool ignoresReturnValue_ : 1;

  bool needsClassCheck_ : 1;
  bool maybeCrossRealm_ : 1;
  bool needsThisCheck_ : 1;

  MCall(WrappedFunction* target, uint32_t numActualArgs, bool construct,
        bool ignoresReturnValue)
      : MCallBase(classOpcode),
        target_(target),
        numActualArgs_(numActualArgs),
        construct_(construct),
        ignoresReturnValue_(ignoresReturnValue),
        needsClassCheck_(true),
        maybeCrossRealm_(true),
        needsThisCheck_(false) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(Call)
  static MCall* New(TempAllocator& alloc, WrappedFunction* target,
                    size_t maxArgc, size_t numActualArgs, bool construct,
                    bool ignoresReturnValue, bool isDOMCall,
                    mozilla::Maybe<DOMObjectKind> objectKind);

  bool needsClassCheck() const { return needsClassCheck_; }
  void disableClassCheck() { needsClassCheck_ = false; }

  bool maybeCrossRealm() const { return maybeCrossRealm_; }
  void setNotCrossRealm() { maybeCrossRealm_ = false; }

  bool needsThisCheck() const { return needsThisCheck_; }
  void setNeedsThisCheck() {
    MOZ_ASSERT(construct_);
    needsThisCheck_ = true;
  }

  // For monomorphic callsites.
  WrappedFunction* getSingleTarget() const { return target_; }

  bool isConstructing() const { return construct_; }

  bool ignoresReturnValue() const { return ignoresReturnValue_; }

  // Does not include |this|.
  uint32_t numActualArgs() const { return numActualArgs_; }

  bool possiblyCalls() const override { return true; }

  virtual bool isCallDOMNative() const { return false; }

  // A method that can be called to tell the MCall to figure out whether it's
  // movable or not.  This can't be done in the constructor, because it
  // depends on the arguments to the call, and those aren't passed to the
  // constructor but are set up later via addArg.
  virtual void computeMovable() {}
};

class MCallDOMNative : public MCall {
  // A helper class for MCalls for DOM natives.  Note that this is NOT
  // actually a separate MIR op from MCall, because all sorts of places use
  // isCall() to check for calls and all we really want is to overload a few
  // virtual things from MCall.

  DOMObjectKind objectKind_;

  MCallDOMNative(WrappedFunction* target, uint32_t numActualArgs,
                 DOMObjectKind objectKind)
      : MCall(target, numActualArgs, false, false), objectKind_(objectKind) {
    MOZ_ASSERT(getJitInfo()->type() != JSJitInfo::InlinableNative);

    // If our jitinfo is not marked eliminatable, that means that our C++
    // implementation is fallible or that it never wants to be eliminated or
    // that we have no hope of ever doing the sort of argument analysis that
    // would allow us to detemine that we're side-effect-free.  In the
    // latter case we wouldn't get DCEd no matter what, but for the former
    // two cases we have to explicitly say that we can't be DCEd.
    if (!getJitInfo()->isEliminatable) {
      setGuard();
    }
  }

  friend MCall* MCall::New(TempAllocator& alloc, WrappedFunction* target,
                           size_t maxArgc, size_t numActualArgs, bool construct,
                           bool ignoresReturnValue, bool isDOMCall,
                           mozilla::Maybe<DOMObjectKind> objectKind);

  const JSJitInfo* getJitInfo() const;

 public:
  DOMObjectKind objectKind() const { return objectKind_; }

  virtual AliasSet getAliasSet() const override;

  virtual bool congruentTo(const MDefinition* ins) const override;

  virtual bool isCallDOMNative() const override { return true; }

  virtual void computeMovable() override;
};

// Used to invoke a JSClass call/construct hook.
class MCallClassHook : public MCallBase {
  const JSNative target_;
  bool constructing_ : 1;
  bool ignoresReturnValue_ : 1;

  MCallClassHook(JSNative target, bool constructing)
      : MCallBase(classOpcode),
        target_(target),
        constructing_(constructing),
        ignoresReturnValue_(false) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(CallClassHook)
  static MCallClassHook* New(TempAllocator& alloc, JSNative target,
                             uint32_t argc, bool constructing);

  JSNative target() const { return target_; }
  bool isConstructing() const { return constructing_; }

  uint32_t numActualArgs() const {
    uint32_t thisAndNewTarget = 1 + constructing_;
    MOZ_ASSERT(numStackArgs() >= thisAndNewTarget);
    return numStackArgs() - thisAndNewTarget;
  }

  bool maybeCrossRealm() const { return true; }

  bool ignoresReturnValue() const { return ignoresReturnValue_; }
  void setIgnoresReturnValue() { ignoresReturnValue_ = true; }

  bool possiblyCalls() const override { return true; }
};

// fun.apply(self, arguments)
class MApplyArgs : public MTernaryInstruction,
                   public MixPolicy<ObjectPolicy<0>, UnboxedInt32Policy<1>,
                                    BoxPolicy<2>>::Data {
  // Single target from CacheIR, or nullptr
  WrappedFunction* target_;
  // Number of extra initial formals to skip.
  uint32_t numExtraFormals_;
  bool maybeCrossRealm_ = true;
  bool ignoresReturnValue_ = false;

  MApplyArgs(WrappedFunction* target, MDefinition* fun, MDefinition* argc,
             MDefinition* self, uint32_t numExtraFormals = 0)
      : MTernaryInstruction(classOpcode, fun, argc, self),
        target_(target),
        numExtraFormals_(numExtraFormals) {
    MOZ_ASSERT(argc->type() == MIRType::Int32);
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(ApplyArgs)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, getFunction), (1, getArgc), (2, getThis))

  WrappedFunction* getSingleTarget() const { return target_; }

  uint32_t numExtraFormals() const { return numExtraFormals_; }

  bool maybeCrossRealm() const { return maybeCrossRealm_; }
  void setNotCrossRealm() { maybeCrossRealm_ = false; }

  bool ignoresReturnValue() const { return ignoresReturnValue_; }
  void setIgnoresReturnValue() { ignoresReturnValue_ = true; }

  bool isConstructing() const { return false; }

  bool possiblyCalls() const override { return true; }
};

class MApplyArgsObj
    : public MTernaryInstruction,
      public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1>, BoxPolicy<2>>::Data {
  WrappedFunction* target_;
  bool maybeCrossRealm_ = true;
  bool ignoresReturnValue_ = false;

  MApplyArgsObj(WrappedFunction* target, MDefinition* fun, MDefinition* argsObj,
                MDefinition* thisArg)
      : MTernaryInstruction(classOpcode, fun, argsObj, thisArg),
        target_(target) {
    MOZ_ASSERT(argsObj->type() == MIRType::Object);
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(ApplyArgsObj)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, getFunction), (1, getArgsObj), (2, getThis))

  WrappedFunction* getSingleTarget() const { return target_; }

  bool maybeCrossRealm() const { return maybeCrossRealm_; }
  void setNotCrossRealm() { maybeCrossRealm_ = false; }

  bool ignoresReturnValue() const { return ignoresReturnValue_; }
  void setIgnoresReturnValue() { ignoresReturnValue_ = true; }

  bool isConstructing() const { return false; }

  bool possiblyCalls() const override { return true; }
};

// fun.apply(fn, array)
class MApplyArray : public MTernaryInstruction,
                    public MixPolicy<ObjectPolicy<0>, BoxPolicy<2>>::Data {
  // Single target from CacheIR, or nullptr
  WrappedFunction* target_;
  bool maybeCrossRealm_ = true;
  bool ignoresReturnValue_ = false;

  MApplyArray(WrappedFunction* target, MDefinition* fun, MDefinition* elements,
              MDefinition* self)
      : MTernaryInstruction(classOpcode, fun, elements, self), target_(target) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(ApplyArray)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, getFunction), (1, getElements), (2, getThis))

  WrappedFunction* getSingleTarget() const { return target_; }

  bool maybeCrossRealm() const { return maybeCrossRealm_; }
  void setNotCrossRealm() { maybeCrossRealm_ = false; }

  bool ignoresReturnValue() const { return ignoresReturnValue_; }
  void setIgnoresReturnValue() { ignoresReturnValue_ = true; }

  bool isConstructing() const { return false; }

  bool possiblyCalls() const override { return true; }
};

// |new F(...arguments)| and |super(...arguments)|.
class MConstructArgs : public MQuaternaryInstruction,
                       public MixPolicy<ObjectPolicy<0>, UnboxedInt32Policy<1>,
                                        BoxPolicy<2>, ObjectPolicy<3>>::Data {
  // Single target from CacheIR, or nullptr
  WrappedFunction* target_;
  // Number of extra initial formals to skip.
  uint32_t numExtraFormals_;
  bool maybeCrossRealm_ = true;

  MConstructArgs(WrappedFunction* target, MDefinition* fun, MDefinition* argc,
                 MDefinition* thisValue, MDefinition* newTarget,
                 uint32_t numExtraFormals = 0)
      : MQuaternaryInstruction(classOpcode, fun, argc, thisValue, newTarget),
        target_(target),
        numExtraFormals_(numExtraFormals) {
    MOZ_ASSERT(argc->type() == MIRType::Int32);
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(ConstructArgs)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, getFunction), (1, getArgc), (2, getThis),
                 (3, getNewTarget))

  WrappedFunction* getSingleTarget() const { return target_; }

  uint32_t numExtraFormals() const { return numExtraFormals_; }

  bool maybeCrossRealm() const { return maybeCrossRealm_; }
  void setNotCrossRealm() { maybeCrossRealm_ = false; }

  bool ignoresReturnValue() const { return false; }
  bool isConstructing() const { return true; }

  bool possiblyCalls() const override { return true; }
};

// |new F(...args)| and |super(...args)|.
class MConstructArray
    : public MQuaternaryInstruction,
      public MixPolicy<ObjectPolicy<0>, BoxPolicy<2>, ObjectPolicy<3>>::Data {
  // Single target from CacheIR, or nullptr
  WrappedFunction* target_;
  bool maybeCrossRealm_ = true;
  bool needsThisCheck_ = false;

  MConstructArray(WrappedFunction* target, MDefinition* fun,
                  MDefinition* elements, MDefinition* thisValue,
                  MDefinition* newTarget)
      : MQuaternaryInstruction(classOpcode, fun, elements, thisValue,
                               newTarget),
        target_(target) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(ConstructArray)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, getFunction), (1, getElements), (2, getThis),
                 (3, getNewTarget))

  WrappedFunction* getSingleTarget() const { return target_; }

  bool maybeCrossRealm() const { return maybeCrossRealm_; }
  void setNotCrossRealm() { maybeCrossRealm_ = false; }

  bool needsThisCheck() const { return needsThisCheck_; }
  void setNeedsThisCheck() { needsThisCheck_ = true; }

  bool ignoresReturnValue() const { return false; }
  bool isConstructing() const { return true; }

  bool possiblyCalls() const override { return true; }
};

class MBail : public MNullaryInstruction {
  explicit MBail(BailoutKind kind) : MNullaryInstruction(classOpcode) {
    setBailoutKind(kind);
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(Bail)

  static MBail* New(TempAllocator& alloc, BailoutKind kind) {
    return new (alloc) MBail(kind);
  }
  static MBail* New(TempAllocator& alloc) {
    return new (alloc) MBail(BailoutKind::Inevitable);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MUnreachable : public MAryControlInstruction<0, 0>,
                     public NoTypePolicy::Data {
  MUnreachable() : MAryControlInstruction(classOpcode) {}

 public:
  INSTRUCTION_HEADER(Unreachable)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MAssertRecoveredOnBailout : public MUnaryInstruction,
                                  public NoTypePolicy::Data {
  bool mustBeRecovered_;

  MAssertRecoveredOnBailout(MDefinition* ins, bool mustBeRecovered)
      : MUnaryInstruction(classOpcode, ins), mustBeRecovered_(mustBeRecovered) {
    setResultType(MIRType::Value);
    setRecoveredOnBailout();
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(AssertRecoveredOnBailout)
  TRIVIAL_NEW_WRAPPERS

  // Needed to assert that float32 instructions are correctly recovered.
  bool canConsumeFloat32(MUse* use) const override { return true; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MAssertFloat32 : public MUnaryInstruction, public NoTypePolicy::Data {
  bool mustBeFloat32_;

  MAssertFloat32(MDefinition* value, bool mustBeFloat32)
      : MUnaryInstruction(classOpcode, value), mustBeFloat32_(mustBeFloat32) {}

 public:
  INSTRUCTION_HEADER(AssertFloat32)
  TRIVIAL_NEW_WRAPPERS

  bool canConsumeFloat32(MUse* use) const override { return true; }

  bool mustBeFloat32() const { return mustBeFloat32_; }
};

class MCompare : public MBinaryInstruction, public ComparePolicy::Data {
 public:
  enum CompareType {

    // Anything compared to Undefined
    Compare_Undefined,

    // Anything compared to Null
    Compare_Null,

    // Int32   compared to Int32
    // Boolean compared to Boolean
    Compare_Int32,

    // Int32 compared as unsigneds
    Compare_UInt32,

    // Int64 compared to Int64.
    Compare_Int64,

    // Int64 compared as unsigneds.
    Compare_UInt64,

    // IntPtr compared as unsigneds.
    Compare_UIntPtr,

    // Double compared to Double
    Compare_Double,

    // Float compared to Float
    Compare_Float32,

    // String compared to String
    Compare_String,

    // Symbol compared to Symbol
    Compare_Symbol,

    // Object compared to Object
    Compare_Object,

    // BigInt compared to BigInt
    Compare_BigInt,

    // BigInt compared to Int32
    Compare_BigInt_Int32,

    // BigInt compared to Double
    Compare_BigInt_Double,

    // BigInt compared to String
    Compare_BigInt_String,

    // Wasm Ref/AnyRef/NullRef compared to Ref/AnyRef/NullRef
    Compare_RefOrNull,
  };

 private:
  CompareType compareType_;
  JSOp jsop_;
  bool operandsAreNeverNaN_;

  // When a floating-point comparison is converted to an integer comparison
  // (when range analysis proves it safe), we need to convert the operands
  // to integer as well.
  bool truncateOperands_;

  MCompare(MDefinition* left, MDefinition* right, JSOp jsop,
           CompareType compareType)
      : MBinaryInstruction(classOpcode, left, right),
        compareType_(compareType),
        jsop_(jsop),
        operandsAreNeverNaN_(false),
        truncateOperands_(false) {
    setResultType(MIRType::Boolean);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Compare)
  TRIVIAL_NEW_WRAPPERS

  static MCompare* NewWasm(TempAllocator& alloc, MDefinition* left,
                           MDefinition* right, JSOp jsop,
                           CompareType compareType) {
    MOZ_ASSERT(compareType == Compare_Int32 || compareType == Compare_UInt32 ||
               compareType == Compare_Int64 || compareType == Compare_UInt64 ||
               compareType == Compare_Double ||
               compareType == Compare_Float32 ||
               compareType == Compare_RefOrNull);
    auto* ins = MCompare::New(alloc, left, right, jsop, compareType);
    ins->setResultType(MIRType::Int32);
    return ins;
  }

  [[nodiscard]] bool tryFold(bool* result);
  [[nodiscard]] bool evaluateConstantOperands(TempAllocator& alloc,
                                              bool* result);
  MDefinition* foldsTo(TempAllocator& alloc) override;

  CompareType compareType() const { return compareType_; }
  bool isInt32Comparison() const { return compareType() == Compare_Int32; }
  bool isDoubleComparison() const { return compareType() == Compare_Double; }
  bool isFloat32Comparison() const { return compareType() == Compare_Float32; }
  bool isNumericComparison() const {
    return isInt32Comparison() || isDoubleComparison() || isFloat32Comparison();
  }
  MIRType inputType();

  JSOp jsop() const { return jsop_; }
  bool operandsAreNeverNaN() const { return operandsAreNeverNaN_; }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

#ifdef JS_JITSPEW
  void printOpcode(GenericPrinter& out) const override;
#endif
  void collectRangeInfoPreTrunc() override;

  void trySpecializeFloat32(TempAllocator& alloc) override;
  bool isFloat32Commutative() const override { return true; }
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  TruncateKind operandTruncateKind(size_t index) const override;

#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override {
    // Both sides of the compare can be Float32
    return compareType_ == Compare_Float32;
  }
#endif

  ALLOW_CLONE(MCompare)

 private:
  [[nodiscard]] bool tryFoldEqualOperands(bool* result);
  [[nodiscard]] bool tryFoldTypeOf(bool* result);
  [[nodiscard]] MDefinition* tryFoldTypeOf(TempAllocator& alloc);
  [[nodiscard]] MDefinition* tryFoldCharCompare(TempAllocator& alloc);
  [[nodiscard]] MDefinition* tryFoldStringCompare(TempAllocator& alloc);
  [[nodiscard]] MDefinition* tryFoldStringSubstring(TempAllocator& alloc);
  [[nodiscard]] MDefinition* tryFoldStringIndexOf(TempAllocator& alloc);

 public:
  bool congruentTo(const MDefinition* ins) const override {
    if (!binaryCongruentTo(ins)) {
      return false;
    }
    return compareType() == ins->toCompare()->compareType() &&
           jsop() == ins->toCompare()->jsop();
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override {
    switch (compareType_) {
      case Compare_Undefined:
      case Compare_Null:
      case Compare_Int32:
      case Compare_UInt32:
      case Compare_Double:
      case Compare_Float32:
      case Compare_String:
      case Compare_Symbol:
      case Compare_Object:
      case Compare_BigInt:
      case Compare_BigInt_Int32:
      case Compare_BigInt_Double:
      case Compare_BigInt_String:
        return true;

      case Compare_Int64:
      case Compare_UInt64:
      case Compare_UIntPtr:
      case Compare_RefOrNull:
        return false;
    }
    MOZ_CRASH("unexpected compare type");
  }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    const char* ty = nullptr;
    switch (compareType_) {
      case Compare_Undefined:
        ty = "Undefined";
        break;
      case Compare_Null:
        ty = "Null";
        break;
      case Compare_Int32:
        ty = "Int32";
        break;
      case Compare_UInt32:
        ty = "UInt32";
        break;
      case Compare_Int64:
        ty = "Int64";
        break;
      case Compare_UInt64:
        ty = "UInt64";
        break;
      case Compare_UIntPtr:
        ty = "UIntPtr";
        break;
      case Compare_Double:
        ty = "Double";
        break;
      case Compare_Float32:
        ty = "Float32";
        break;
      case Compare_String:
        ty = "String";
        break;
      case Compare_Symbol:
        ty = "Symbol";
        break;
      case Compare_Object:
        ty = "Object";
        break;
      case Compare_BigInt:
        ty = "BigInt";
        break;
      case Compare_BigInt_Int32:
        ty = "BigInt_Int32";
        break;
      case Compare_BigInt_Double:
        ty = "BigInt_Double";
        break;
      case Compare_BigInt_String:
        ty = "BigInt_String";
        break;
      case Compare_RefOrNull:
        ty = "RefOrNull";
        break;
      default:
        ty = "!!unknown!!";
        break;
    };
    char buf[64];
    SprintfLiteral(buf, "ty=%s jsop=%s", ty, CodeName(jsop()));
    extras->add(buf);
  }
#endif
};

// Takes a typed value and returns an untyped value.
class MBox : public MUnaryInstruction, public NoTypePolicy::Data {
  explicit MBox(MDefinition* ins) : MUnaryInstruction(classOpcode, ins) {
    // Cannot box a box.
    MOZ_ASSERT(ins->type() != MIRType::Value);

    setResultType(MIRType::Value);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Box)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MBox)
};

// Note: the op may have been inverted during lowering (to put constants in a
// position where they can be immediates), so it is important to use the
// lir->jsop() instead of the mir->jsop() when it is present.
static inline Assembler::Condition JSOpToCondition(
    MCompare::CompareType compareType, JSOp op) {
  bool isSigned = (compareType != MCompare::Compare_UInt32 &&
                   compareType != MCompare::Compare_UInt64 &&
                   compareType != MCompare::Compare_UIntPtr);
  return JSOpToCondition(op, isSigned);
}

// Takes a typed value and checks if it is a certain type. If so, the payload
// is unpacked and returned as that type. Otherwise, it is considered a
// deoptimization.
class MUnbox final : public MUnaryInstruction, public BoxInputsPolicy::Data {
 public:
  enum Mode {
    Fallible,    // Check the type, and deoptimize if unexpected.
    Infallible,  // Type guard is not necessary.
  };

 private:
  Mode mode_;

  MUnbox(MDefinition* ins, MIRType type, Mode mode)
      : MUnaryInstruction(classOpcode, ins), mode_(mode) {
    // Only allow unboxing a non MIRType::Value when input and output types
    // don't match. This is often used to force a bailout. Boxing happens
    // during type analysis.
    MOZ_ASSERT_IF(ins->type() != MIRType::Value, type != ins->type());

    MOZ_ASSERT(type == MIRType::Boolean || type == MIRType::Int32 ||
               type == MIRType::Double || type == MIRType::String ||
               type == MIRType::Symbol || type == MIRType::BigInt ||
               type == MIRType::Object);

    setResultType(type);
    setMovable();

    if (mode_ == Fallible) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(Unbox)
  TRIVIAL_NEW_WRAPPERS

  Mode mode() const { return mode_; }
  bool fallible() const { return mode() != Infallible; }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isUnbox() || ins->toUnbox()->mode() != mode()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }
#ifdef JS_JITSPEW
  void printOpcode(GenericPrinter& out) const override;
#endif

  ALLOW_CLONE(MUnbox)
};

class MAssertRange : public MUnaryInstruction, public NoTypePolicy::Data {
  // This is the range checked by the assertion. Don't confuse this with the
  // range_ member or the range() accessor. Since MAssertRange doesn't return
  // a value, it doesn't use those.
  const Range* assertedRange_;

  MAssertRange(MDefinition* ins, const Range* assertedRange)
      : MUnaryInstruction(classOpcode, ins), assertedRange_(assertedRange) {
    setGuard();
    setResultType(MIRType::None);
  }

 public:
  INSTRUCTION_HEADER(AssertRange)
  TRIVIAL_NEW_WRAPPERS

  const Range* assertedRange() const { return assertedRange_; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

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

class MAssertClass : public MUnaryInstruction, public NoTypePolicy::Data {
  const JSClass* class_;

  MAssertClass(MDefinition* obj, const JSClass* clasp)
      : MUnaryInstruction(classOpcode, obj), class_(clasp) {
    MOZ_ASSERT(obj->type() == MIRType::Object);

    setGuard();
    setResultType(MIRType::None);
  }

 public:
  INSTRUCTION_HEADER(AssertClass)
  TRIVIAL_NEW_WRAPPERS

  const JSClass* getClass() const { return class_; }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MAssertShape : public MUnaryInstruction, public NoTypePolicy::Data {
  CompilerShape shape_;

  MAssertShape(MDefinition* obj, Shape* shape)
      : MUnaryInstruction(classOpcode, obj), shape_(shape) {
    MOZ_ASSERT(obj->type() == MIRType::Object);

    setGuard();
    setResultType(MIRType::None);
  }

 public:
  INSTRUCTION_HEADER(AssertShape)
  TRIVIAL_NEW_WRAPPERS

  const Shape* shape() const { return shape_; }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Eager initialization of arguments object.
class MCreateArgumentsObject : public MUnaryInstruction,
                               public ObjectPolicy<0>::Data {
  CompilerGCPointer<ArgumentsObject*> templateObj_;

  MCreateArgumentsObject(MDefinition* callObj, ArgumentsObject* templateObj)
      : MUnaryInstruction(classOpcode, callObj), templateObj_(templateObj) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(CreateArgumentsObject)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, getCallObject))

  ArgumentsObject* templateObject() const { return templateObj_; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool possiblyCalls() const override { return true; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

// Eager initialization of arguments object for inlined function
class MCreateInlinedArgumentsObject : public MVariadicInstruction,
                                      public NoFloatPolicyAfter<0>::Data {
  CompilerGCPointer<ArgumentsObject*> templateObj_;

  explicit MCreateInlinedArgumentsObject(ArgumentsObject* templateObj)
      : MVariadicInstruction(classOpcode), templateObj_(templateObj) {
    setResultType(MIRType::Object);
  }

  static const size_t NumNonArgumentOperands = 2;

 public:
  INSTRUCTION_HEADER(CreateInlinedArgumentsObject)
  static MCreateInlinedArgumentsObject* New(TempAllocator& alloc,
                                            MDefinition* callObj,
                                            MDefinition* callee,
                                            MDefinitionVector& args,
                                            ArgumentsObject* templateObj);
  NAMED_OPERANDS((0, getCallObject), (1, getCallee))

  ArgumentsObject* templateObject() const { return templateObj_; }

  MDefinition* getArg(uint32_t idx) const {
    return getOperand(idx + NumNonArgumentOperands);
  }
  uint32_t numActuals() const { return numOperands() - NumNonArgumentOperands; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool possiblyCalls() const override { return true; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MGetInlinedArgument
    : public MVariadicInstruction,
      public MixPolicy<UnboxedInt32Policy<0>, NoFloatPolicyAfter<1>>::Data {
  MGetInlinedArgument() : MVariadicInstruction(classOpcode) {
    setResultType(MIRType::Value);
  }

  static const size_t NumNonArgumentOperands = 1;

 public:
  INSTRUCTION_HEADER(GetInlinedArgument)
  static MGetInlinedArgument* New(TempAllocator& alloc, MDefinition* index,
                                  MCreateInlinedArgumentsObject* args);
  static MGetInlinedArgument* New(TempAllocator& alloc, MDefinition* index,
                                  const CallInfo& callInfo);
  NAMED_OPERANDS((0, index))

  MDefinition* getArg(uint32_t idx) const {
    return getOperand(idx + NumNonArgumentOperands);
  }
  uint32_t numActuals() const { return numOperands() - NumNonArgumentOperands; }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;
};

class MGetInlinedArgumentHole
    : public MVariadicInstruction,
      public MixPolicy<UnboxedInt32Policy<0>, NoFloatPolicyAfter<1>>::Data {
  MGetInlinedArgumentHole() : MVariadicInstruction(classOpcode) {
    setGuard();
    setResultType(MIRType::Value);
  }

  static const size_t NumNonArgumentOperands = 1;

 public:
  INSTRUCTION_HEADER(GetInlinedArgumentHole)
  static MGetInlinedArgumentHole* New(TempAllocator& alloc, MDefinition* index,
                                      MCreateInlinedArgumentsObject* args);
  NAMED_OPERANDS((0, index))

  MDefinition* getArg(uint32_t idx) const {
    return getOperand(idx + NumNonArgumentOperands);
  }
  uint32_t numActuals() const { return numOperands() - NumNonArgumentOperands; }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;
};

class MInlineArgumentsSlice
    : public MVariadicInstruction,
      public MixPolicy<UnboxedInt32Policy<0>, UnboxedInt32Policy<1>,
                       NoFloatPolicyAfter<2>>::Data {
  JSObject* templateObj_;
  gc::Heap initialHeap_;

  MInlineArgumentsSlice(JSObject* templateObj, gc::Heap initialHeap)
      : MVariadicInstruction(classOpcode),
        templateObj_(templateObj),
        initialHeap_(initialHeap) {
    setResultType(MIRType::Object);
  }

  static const size_t NumNonArgumentOperands = 2;

 public:
  INSTRUCTION_HEADER(InlineArgumentsSlice)
  static MInlineArgumentsSlice* New(TempAllocator& alloc, MDefinition* begin,
                                    MDefinition* count,
                                    MCreateInlinedArgumentsObject* args,
                                    JSObject* templateObj,
                                    gc::Heap initialHeap);
  NAMED_OPERANDS((0, begin), (1, count))

  JSObject* templateObj() const { return templateObj_; }
  gc::Heap initialHeap() const { return initialHeap_; }

  MDefinition* getArg(uint32_t idx) const {
    return getOperand(idx + NumNonArgumentOperands);
  }
  uint32_t numActuals() const { return numOperands() - NumNonArgumentOperands; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool possiblyCalls() const override { return true; }
};

// Allocates a new BoundFunctionObject and calls
// BoundFunctionObject::functionBindImpl. This instruction can have arbitrary
// side-effects because the GetProperty calls for length/name can call into JS.
class MBindFunction
    : public MVariadicInstruction,
      public MixPolicy<ObjectPolicy<0>, NoFloatPolicyAfter<1>>::Data {
  CompilerGCPointer<JSObject*> templateObj_;

  explicit MBindFunction(JSObject* templateObj)
      : MVariadicInstruction(classOpcode), templateObj_(templateObj) {
    setResultType(MIRType::Object);
  }

  // The target object is operand 0.
  static const size_t NumNonArgumentOperands = 1;

 public:
  INSTRUCTION_HEADER(BindFunction)
  static MBindFunction* New(TempAllocator& alloc, MDefinition* target,
                            uint32_t argc, JSObject* templateObj);
  NAMED_OPERANDS((0, target))

  JSObject* templateObject() const { return templateObj_; }

  MDefinition* getArg(uint32_t idx) const {
    return getOperand(idx + NumNonArgumentOperands);
  }
  void initArg(size_t i, MDefinition* arg) {
    initOperand(NumNonArgumentOperands + i, arg);
  }
  uint32_t numStackArgs() const {
    return numOperands() - NumNonArgumentOperands;
  }

  bool possiblyCalls() const override { return true; }
};

class MToFPInstruction : public MUnaryInstruction, public ToDoublePolicy::Data {
 public:
  // Types of values which can be converted.
  enum ConversionKind { NonStringPrimitives, NumbersOnly };

 private:
  ConversionKind conversion_;

 protected:
  MToFPInstruction(Opcode op, MDefinition* def,
                   ConversionKind conversion = NonStringPrimitives)
      : MUnaryInstruction(op, def), conversion_(conversion) {}

 public:
  ConversionKind conversion() const { return conversion_; }
};

// Converts a primitive (either typed or untyped) to a double. If the input is
// not primitive at runtime, a bailout occurs.
class MToDouble : public MToFPInstruction {
 private:
  TruncateKind implicitTruncate_;

  explicit MToDouble(MDefinition* def,
                     ConversionKind conversion = NonStringPrimitives)
      : MToFPInstruction(classOpcode, def, conversion),
        implicitTruncate_(TruncateKind::NoTruncate) {
    setResultType(MIRType::Double);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (!def->definitelyType({MIRType::Undefined, MIRType::Null,
                              MIRType::Boolean, MIRType::Int32, MIRType::Double,
                              MIRType::Float32, MIRType::String})) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(ToDouble)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isToDouble() || ins->toToDouble()->conversion() != conversion()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  TruncateKind operandTruncateKind(size_t index) const override;

#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif

  TruncateKind truncateKind() const { return implicitTruncate_; }
  void setTruncateKind(TruncateKind kind) {
    implicitTruncate_ = std::max(implicitTruncate_, kind);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override {
    if (input()->type() == MIRType::Value) {
      return false;
    }
    if (input()->type() == MIRType::Symbol) {
      return false;
    }
    if (input()->type() == MIRType::BigInt) {
      return false;
    }

    return true;
  }

  ALLOW_CLONE(MToDouble)
};

// Converts a primitive (either typed or untyped) to a float32. If the input is
// not primitive at runtime, a bailout occurs.
class MToFloat32 : public MToFPInstruction {
  bool mustPreserveNaN_;

  explicit MToFloat32(MDefinition* def,
                      ConversionKind conversion = NonStringPrimitives)
      : MToFPInstruction(classOpcode, def, conversion),
        mustPreserveNaN_(false) {
    setResultType(MIRType::Float32);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (!def->definitelyType({MIRType::Undefined, MIRType::Null,
                              MIRType::Boolean, MIRType::Int32, MIRType::Double,
                              MIRType::Float32, MIRType::String})) {
      setGuard();
    }
  }

  explicit MToFloat32(MDefinition* def, bool mustPreserveNaN)
      : MToFloat32(def) {
    mustPreserveNaN_ = mustPreserveNaN;
  }

 public:
  INSTRUCTION_HEADER(ToFloat32)
  TRIVIAL_NEW_WRAPPERS

  virtual MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!congruentIfOperandsEqual(ins)) {
      return false;
    }
    auto* other = ins->toToFloat32();
    return other->conversion() == conversion() &&
           other->mustPreserveNaN_ == mustPreserveNaN_;
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;

  bool canConsumeFloat32(MUse* use) const override { return true; }
  bool canProduceFloat32() const override { return true; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MToFloat32)
};

// Converts a uint32 to a float32 (coming from wasm).
class MWasmUnsignedToFloat32 : public MUnaryInstruction,
                               public NoTypePolicy::Data {
  explicit MWasmUnsignedToFloat32(MDefinition* def)
      : MUnaryInstruction(classOpcode, def) {
    setResultType(MIRType::Float32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmUnsignedToFloat32)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool canProduceFloat32() const override { return true; }
};

class MWrapInt64ToInt32 : public MUnaryInstruction, public NoTypePolicy::Data {
  bool bottomHalf_;

  explicit MWrapInt64ToInt32(MDefinition* def, bool bottomHalf = true)
      : MUnaryInstruction(classOpcode, def), bottomHalf_(bottomHalf) {
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WrapInt64ToInt32)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isWrapInt64ToInt32()) {
      return false;
    }
    if (ins->toWrapInt64ToInt32()->bottomHalf() != bottomHalf()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool bottomHalf() const { return bottomHalf_; }
};

class MExtendInt32ToInt64 : public MUnaryInstruction,
                            public NoTypePolicy::Data {
  bool isUnsigned_;

  MExtendInt32ToInt64(MDefinition* def, bool isUnsigned)
      : MUnaryInstruction(classOpcode, def), isUnsigned_(isUnsigned) {
    setResultType(MIRType::Int64);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(ExtendInt32ToInt64)
  TRIVIAL_NEW_WRAPPERS

  bool isUnsigned() const { return isUnsigned_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isExtendInt32ToInt64()) {
      return false;
    }
    if (ins->toExtendInt32ToInt64()->isUnsigned_ != isUnsigned_) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// The same as MWasmTruncateToInt64 but with the Instance dependency.
// It used only for arm now because on arm we need to call builtin to truncate
// to i64.
class MWasmBuiltinTruncateToInt64 : public MAryInstruction<2>,
                                    public NoTypePolicy::Data {
  TruncFlags flags_;
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmBuiltinTruncateToInt64(MDefinition* def, MDefinition* instance,
                              TruncFlags flags,
                              wasm::BytecodeOffset bytecodeOffset)
      : MAryInstruction(classOpcode),
        flags_(flags),
        bytecodeOffset_(bytecodeOffset) {
    initOperand(0, def);
    initOperand(1, instance);

    setResultType(MIRType::Int64);
    setGuard();  // neither removable nor movable because of possible
                 // side-effects.
  }

 public:
  INSTRUCTION_HEADER(WasmBuiltinTruncateToInt64)
  NAMED_OPERANDS((0, input), (1, instance));
  TRIVIAL_NEW_WRAPPERS

  bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
  bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
  TruncFlags flags() const { return flags_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toWasmBuiltinTruncateToInt64()->flags() == flags_;
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MWasmTruncateToInt64 : public MUnaryInstruction,
                             public NoTypePolicy::Data {
  TruncFlags flags_;
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmTruncateToInt64(MDefinition* def, TruncFlags flags,
                       wasm::BytecodeOffset bytecodeOffset)
      : MUnaryInstruction(classOpcode, def),
        flags_(flags),
        bytecodeOffset_(bytecodeOffset) {
    setResultType(MIRType::Int64);
    setGuard();  // neither removable nor movable because of possible
                 // side-effects.
  }

 public:
  INSTRUCTION_HEADER(WasmTruncateToInt64)
  TRIVIAL_NEW_WRAPPERS

  bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
  bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
  TruncFlags flags() const { return flags_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toWasmTruncateToInt64()->flags() == flags_;
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Truncate a value to an int32, with wasm semantics: this will trap when the
// value is out of range.
class MWasmTruncateToInt32 : public MUnaryInstruction,
                             public NoTypePolicy::Data {
  TruncFlags flags_;
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MWasmTruncateToInt32(MDefinition* def, TruncFlags flags,
                                wasm::BytecodeOffset bytecodeOffset)
      : MUnaryInstruction(classOpcode, def),
        flags_(flags),
        bytecodeOffset_(bytecodeOffset) {
    setResultType(MIRType::Int32);
    setGuard();  // neither removable nor movable because of possible
                 // side-effects.
  }

 public:
  INSTRUCTION_HEADER(WasmTruncateToInt32)
  TRIVIAL_NEW_WRAPPERS

  bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
  bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
  TruncFlags flags() const { return flags_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toWasmTruncateToInt32()->flags() == flags_;
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Converts an int32 value to intptr by sign-extending it.
class MInt32ToIntPtr : public MUnaryInstruction,
                       public UnboxedInt32Policy<0>::Data {
  bool canBeNegative_ = true;

  explicit MInt32ToIntPtr(MDefinition* def)
      : MUnaryInstruction(classOpcode, def) {
    setResultType(MIRType::IntPtr);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Int32ToIntPtr)
  TRIVIAL_NEW_WRAPPERS

  bool canBeNegative() const { return canBeNegative_; }
  void setCanNotBeNegative() { canBeNegative_ = false; }

  void computeRange(TempAllocator& alloc) override;
  void collectRangeInfoPreTrunc() override;

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Converts an IntPtr value >= 0 to Int32. Bails out if the value > INT32_MAX.
class MNonNegativeIntPtrToInt32 : public MUnaryInstruction,
                                  public NoTypePolicy::Data {
  explicit MNonNegativeIntPtrToInt32(MDefinition* def)
      : MUnaryInstruction(classOpcode, def) {
    MOZ_ASSERT(def->type() == MIRType::IntPtr);
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(NonNegativeIntPtrToInt32)
  TRIVIAL_NEW_WRAPPERS

  void computeRange(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Converts an IntPtr value to Double.
class MIntPtrToDouble : public MUnaryInstruction, public NoTypePolicy::Data {
  explicit MIntPtrToDouble(MDefinition* def)
      : MUnaryInstruction(classOpcode, def) {
    MOZ_ASSERT(def->type() == MIRType::IntPtr);
    setResultType(MIRType::Double);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(IntPtrToDouble)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Subtracts (byteSize - 1) from the input value. Bails out if the result is
// negative. This is used to implement bounds checks for DataView accesses.
class MAdjustDataViewLength : public MUnaryInstruction,
                              public NoTypePolicy::Data {
  const uint32_t byteSize_;

  MAdjustDataViewLength(MDefinition* input, uint32_t byteSize)
      : MUnaryInstruction(classOpcode, input), byteSize_(byteSize) {
    MOZ_ASSERT(input->type() == MIRType::IntPtr);
    MOZ_ASSERT(byteSize > 1);
    setResultType(MIRType::IntPtr);
    setMovable();
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(AdjustDataViewLength)
  TRIVIAL_NEW_WRAPPERS

  uint32_t byteSize() const { return byteSize_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isAdjustDataViewLength()) {
      return false;
    }
    if (ins->toAdjustDataViewLength()->byteSize() != byteSize()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MInt64ToFloatingPoint : public MUnaryInstruction,
                              public NoTypePolicy::Data {
  bool isUnsigned_;
  wasm::BytecodeOffset bytecodeOffset_;

  MInt64ToFloatingPoint(MDefinition* def, MIRType type,
                        wasm::BytecodeOffset bytecodeOffset, bool isUnsigned)
      : MUnaryInstruction(classOpcode, def),
        isUnsigned_(isUnsigned),
        bytecodeOffset_(bytecodeOffset) {
    MOZ_ASSERT(IsFloatingPointType(type));
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Int64ToFloatingPoint)
  TRIVIAL_NEW_WRAPPERS

  bool isUnsigned() const { return isUnsigned_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isInt64ToFloatingPoint()) {
      return false;
    }
    if (ins->toInt64ToFloatingPoint()->isUnsigned_ != isUnsigned_) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// It used only for arm now because on arm we need to call builtin to convert
// i64 to float.
class MBuiltinInt64ToFloatingPoint : public MAryInstruction<2>,
                                     public NoTypePolicy::Data {
  bool isUnsigned_;
  wasm::BytecodeOffset bytecodeOffset_;

  MBuiltinInt64ToFloatingPoint(MDefinition* def, MDefinition* instance,
                               MIRType type,
                               wasm::BytecodeOffset bytecodeOffset,
                               bool isUnsigned)
      : MAryInstruction(classOpcode),
        isUnsigned_(isUnsigned),
        bytecodeOffset_(bytecodeOffset) {
    MOZ_ASSERT(IsFloatingPointType(type));
    initOperand(0, def);
    initOperand(1, instance);
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(BuiltinInt64ToFloatingPoint)
  NAMED_OPERANDS((0, input), (1, instance));
  TRIVIAL_NEW_WRAPPERS

  bool isUnsigned() const { return isUnsigned_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isBuiltinInt64ToFloatingPoint()) {
      return false;
    }
    if (ins->toBuiltinInt64ToFloatingPoint()->isUnsigned_ != isUnsigned_) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Applies ECMA's ToNumber on a primitive (either typed or untyped) and expects
// the result to be precisely representable as an Int32, otherwise bails.
//
// If the input is not primitive at runtime, a bailout occurs. If the input
// cannot be converted to an int32 without loss (i.e. 5.5 or undefined) then a
// bailout occurs.
class MToNumberInt32 : public MUnaryInstruction, public ToInt32Policy::Data {
  bool needsNegativeZeroCheck_;
  IntConversionInputKind conversion_;

  explicit MToNumberInt32(MDefinition* def, IntConversionInputKind conversion =
                                                IntConversionInputKind::Any)
      : MUnaryInstruction(classOpcode, def),
        needsNegativeZeroCheck_(true),
        conversion_(conversion) {
    setResultType(MIRType::Int32);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (!def->definitelyType({MIRType::Undefined, MIRType::Null,
                              MIRType::Boolean, MIRType::Int32, MIRType::Double,
                              MIRType::Float32, MIRType::String})) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(ToNumberInt32)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;

  // this only has backwards information flow.
  void analyzeEdgeCasesBackward() override;

  bool needsNegativeZeroCheck() const { return needsNegativeZeroCheck_; }
  void setNeedsNegativeZeroCheck(bool needsCheck) {
    needsNegativeZeroCheck_ = needsCheck;
  }

  IntConversionInputKind conversion() const { return conversion_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isToNumberInt32() ||
        ins->toToNumberInt32()->conversion() != conversion()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;
  void collectRangeInfoPreTrunc() override;

#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif

  ALLOW_CLONE(MToNumberInt32)
};

// Converts a value or typed input to a truncated int32, for use with bitwise
// operations. This is an infallible ValueToECMAInt32.
class MTruncateToInt32 : public MUnaryInstruction, public ToInt32Policy::Data {
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MTruncateToInt32(
      MDefinition* def,
      wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset())
      : MUnaryInstruction(classOpcode, def), bytecodeOffset_(bytecodeOffset) {
    setResultType(MIRType::Int32);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (mightHaveSideEffects(def)) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(TruncateToInt32)
  TRIVIAL_NEW_WRAPPERS

  static bool mightHaveSideEffects(MDefinition* def) {
    return !def->definitelyType(
        {MIRType::Undefined, MIRType::Null, MIRType::Boolean, MIRType::Int32,
         MIRType::Double, MIRType::Float32, MIRType::String});
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;
  TruncateKind operandTruncateKind(size_t index) const override;
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override {
    return input()->type() < MIRType::Symbol;
  }

  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  ALLOW_CLONE(MTruncateToInt32)
};

// It is like MTruncateToInt32 but with instance dependency.
class MWasmBuiltinTruncateToInt32 : public MAryInstruction<2>,
                                    public ToInt32Policy::Data {
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmBuiltinTruncateToInt32(
      MDefinition* def, MDefinition* instance,
      wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset())
      : MAryInstruction(classOpcode), bytecodeOffset_(bytecodeOffset) {
    initOperand(0, def);
    initOperand(1, instance);
    setResultType(MIRType::Int32);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (MTruncateToInt32::mightHaveSideEffects(def)) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(WasmBuiltinTruncateToInt32)
  NAMED_OPERANDS((0, input), (1, instance))
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  ALLOW_CLONE(MWasmBuiltinTruncateToInt32)
};

// Converts a primitive (either typed or untyped) to a BigInt. If the input is
// not primitive at runtime, a bailout occurs.
class MToBigInt : public MUnaryInstruction, public ToBigIntPolicy::Data {
 private:
  explicit MToBigInt(MDefinition* def) : MUnaryInstruction(classOpcode, def) {
    setResultType(MIRType::BigInt);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (!def->definitelyType({MIRType::Boolean, MIRType::BigInt})) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(ToBigInt)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MToBigInt)
};

// Takes a Value or typed input and returns a suitable Int64 using the
// ToBigInt algorithm, possibly calling out to the VM for string, etc inputs.
class MToInt64 : public MUnaryInstruction, public ToInt64Policy::Data {
  explicit MToInt64(MDefinition* def) : MUnaryInstruction(classOpcode, def) {
    setResultType(MIRType::Int64);
    setMovable();

    // Guard unless the conversion is known to be non-effectful & non-throwing.
    if (!def->definitelyType(
            {MIRType::Boolean, MIRType::BigInt, MIRType::Int64})) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(ToInt64)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  ALLOW_CLONE(MToInt64)
};

// Takes a BigInt pointer and returns its toInt64 value.
class MTruncateBigIntToInt64 : public MUnaryInstruction,
                               public NoTypePolicy::Data {
  explicit MTruncateBigIntToInt64(MDefinition* def)
      : MUnaryInstruction(classOpcode, def) {
    MOZ_ASSERT(def->type() == MIRType::BigInt);
    setResultType(MIRType::Int64);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(TruncateBigIntToInt64)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  ALLOW_CLONE(MTruncateBigIntToInt64)
};

// Takes an Int64 and returns a fresh BigInt pointer.
class MInt64ToBigInt : public MUnaryInstruction, public NoTypePolicy::Data {
  explicit MInt64ToBigInt(MDefinition* def)
      : MUnaryInstruction(classOpcode, def) {
    MOZ_ASSERT(def->type() == MIRType::Int64);
    setResultType(MIRType::BigInt);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Int64ToBigInt)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MInt64ToBigInt)
};

// Converts any type to a string
class MToString : public MUnaryInstruction, public ToStringPolicy::Data {
 public:
  // MToString has two modes for handling of object/symbol arguments: if the
  // to-string conversion happens as part of another opcode, we have to bail out
  // to Baseline. If the conversion is for a stand-alone JSOp we can support
  // side-effects.
  enum class SideEffectHandling { Bailout, Supported };

 private:
  SideEffectHandling sideEffects_;
  bool mightHaveSideEffects_ = false;

  MToString(MDefinition* def, SideEffectHandling sideEffects)
      : MUnaryInstruction(classOpcode, def), sideEffects_(sideEffects) {
    setResultType(MIRType::String);

    if (!def->definitelyType({MIRType::Undefined, MIRType::Null,
                              MIRType::Boolean, MIRType::Int32, MIRType::Double,
                              MIRType::Float32, MIRType::String,
                              MIRType::BigInt})) {
      mightHaveSideEffects_ = true;
    }

    // If this instruction is not effectful, mark it as movable and set the
    // Guard flag if needed. If the operation is effectful it won't be
    // optimized anyway so there's no need to set any flags.
    if (!isEffectful()) {
      setMovable();
      // Objects might override toString; Symbol throws. We bailout in those
      // cases and run side-effects in baseline instead.
      if (mightHaveSideEffects_) {
        setGuard();
      }
    }
  }

 public:
  INSTRUCTION_HEADER(ToString)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isToString()) {
      return false;
    }
    if (sideEffects_ != ins->toToString()->sideEffects_) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    if (supportSideEffects() && mightHaveSideEffects_) {
      return AliasSet::Store(AliasSet::Any);
    }
    return AliasSet::None();
  }

  bool mightHaveSideEffects() const { return mightHaveSideEffects_; }

  bool supportSideEffects() const {
    return sideEffects_ == SideEffectHandling::Supported;
  }

  bool needsSnapshot() const {
    return sideEffects_ == SideEffectHandling::Bailout && mightHaveSideEffects_;
  }

  ALLOW_CLONE(MToString)
};

class MBitNot : public MUnaryInstruction, public BitwisePolicy::Data {
  explicit MBitNot(MDefinition* input) : MUnaryInstruction(classOpcode, input) {
    setResultType(MIRType::Int32);
    if (input->type() == MIRType::Int64) {
      setResultType(MIRType::Int64);
    }
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(BitNot)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBitNot)
};

class MTypeOf : public MUnaryInstruction,
                public BoxExceptPolicy<0, MIRType::Object>::Data {
  explicit MTypeOf(MDefinition* def) : MUnaryInstruction(classOpcode, def) {
    setResultType(MIRType::Int32);
    setMovable();
  }
  TypeDataList observed_;

 public:
  INSTRUCTION_HEADER(TypeOf)
  TRIVIAL_NEW_WRAPPERS

  void setObservedTypes(const TypeDataList& observed) { observed_ = observed; }
  bool hasObservedTypes() const { return observed_.count() > 0; }
  const TypeDataList& observedTypes() const { return observed_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MTypeOfIs : public MUnaryInstruction, public NoTypePolicy::Data {
  JSOp jsop_;
  JSType jstype_;

  MTypeOfIs(MDefinition* def, JSOp jsop, JSType jstype)
      : MUnaryInstruction(classOpcode, def), jsop_(jsop), jstype_(jstype) {
    MOZ_ASSERT(def->type() == MIRType::Object || def->type() == MIRType::Value);

    setResultType(MIRType::Boolean);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(TypeOfIs)
  TRIVIAL_NEW_WRAPPERS

  JSOp jsop() const { return jsop_; }
  JSType jstype() const { return jstype_; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    if (!congruentIfOperandsEqual(ins)) {
      return false;
    }
    return jsop() == ins->toTypeOfIs()->jsop() &&
           jstype() == ins->toTypeOfIs()->jstype();
  }

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

class MBinaryBitwiseInstruction : public MBinaryInstruction,
                                  public BitwisePolicy::Data {
 protected:
  MBinaryBitwiseInstruction(Opcode op, MDefinition* left, MDefinition* right,
                            MIRType type)
      : MBinaryInstruction(op, left, right),
        maskMatchesLeftRange(false),
        maskMatchesRightRange(false) {
    MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64 ||
               (isUrsh() && type == MIRType::Double));
    setResultType(type);
    setMovable();
  }

  bool maskMatchesLeftRange;
  bool maskMatchesRightRange;

 public:
  MDefinition* foldsTo(TempAllocator& alloc) override;
  MDefinition* foldUnnecessaryBitop();
  virtual MDefinition* foldIfZero(size_t operand) = 0;
  virtual MDefinition* foldIfNegOne(size_t operand) = 0;
  virtual MDefinition* foldIfEqual() = 0;
  virtual MDefinition* foldIfAllBitsSet(size_t operand) = 0;
  void collectRangeInfoPreTrunc() override;

  bool congruentTo(const MDefinition* ins) const override {
    return binaryCongruentTo(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  TruncateKind operandTruncateKind(size_t index) const override;
};

class MBitAnd : public MBinaryBitwiseInstruction {
  MBitAnd(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryBitwiseInstruction(classOpcode, left, right, type) {
    setCommutative();
  }

 public:
  INSTRUCTION_HEADER(BitAnd)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldIfZero(size_t operand) override {
    return getOperand(operand);  // 0 & x => 0;
  }
  MDefinition* foldIfNegOne(size_t operand) override {
    return getOperand(1 - operand);  // x & -1 => x
  }
  MDefinition* foldIfEqual() override {
    return getOperand(0);  // x & x => x;
  }
  MDefinition* foldIfAllBitsSet(size_t operand) override {
    // e.g. for uint16: x & 0xffff => x;
    return getOperand(1 - operand);
  }
  void computeRange(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBitAnd)
};

class MBitOr : public MBinaryBitwiseInstruction {
  MBitOr(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryBitwiseInstruction(classOpcode, left, right, type) {
    setCommutative();
  }

 public:
  INSTRUCTION_HEADER(BitOr)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldIfZero(size_t operand) override {
    return getOperand(1 -
                      operand);  // 0 | x => x, so if ith is 0, return (1-i)th
  }
  MDefinition* foldIfNegOne(size_t operand) override {
    return getOperand(operand);  // x | -1 => -1
  }
  MDefinition* foldIfEqual() override {
    return getOperand(0);  // x | x => x
  }
  MDefinition* foldIfAllBitsSet(size_t operand) override { return this; }
  void computeRange(TempAllocator& alloc) override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBitOr)
};

class MBitXor : public MBinaryBitwiseInstruction {
  MBitXor(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryBitwiseInstruction(classOpcode, left, right, type) {
    setCommutative();
  }

 public:
  INSTRUCTION_HEADER(BitXor)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldIfZero(size_t operand) override {
    return getOperand(1 - operand);  // 0 ^ x => x
  }
  MDefinition* foldIfNegOne(size_t operand) override { return this; }
  MDefinition* foldIfEqual() override { return this; }
  MDefinition* foldIfAllBitsSet(size_t operand) override { return this; }
  void computeRange(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBitXor)
};

class MShiftInstruction : public MBinaryBitwiseInstruction {
 protected:
  MShiftInstruction(Opcode op, MDefinition* left, MDefinition* right,
                    MIRType type)
      : MBinaryBitwiseInstruction(op, left, right, type) {}

 public:
  MDefinition* foldIfNegOne(size_t operand) override { return this; }
  MDefinition* foldIfEqual() override { return this; }
  MDefinition* foldIfAllBitsSet(size_t operand) override { return this; }
};

class MLsh : public MShiftInstruction {
  MLsh(MDefinition* left, MDefinition* right, MIRType type)
      : MShiftInstruction(classOpcode, left, right, type) {}

 public:
  INSTRUCTION_HEADER(Lsh)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldIfZero(size_t operand) override {
    // 0 << x => 0
    // x << 0 => x
    return getOperand(0);
  }

  void computeRange(TempAllocator& alloc) override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MLsh)
};

class MRsh : public MShiftInstruction {
  MRsh(MDefinition* left, MDefinition* right, MIRType type)
      : MShiftInstruction(classOpcode, left, right, type) {}

 public:
  INSTRUCTION_HEADER(Rsh)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldIfZero(size_t operand) override {
    // 0 >> x => 0
    // x >> 0 => x
    return getOperand(0);
  }
  void computeRange(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  ALLOW_CLONE(MRsh)
};

class MUrsh : public MShiftInstruction {
  bool bailoutsDisabled_;

  MUrsh(MDefinition* left, MDefinition* right, MIRType type)
      : MShiftInstruction(classOpcode, left, right, type),
        bailoutsDisabled_(false) {}

 public:
  INSTRUCTION_HEADER(Ursh)
  TRIVIAL_NEW_WRAPPERS

  static MUrsh* NewWasm(TempAllocator& alloc, MDefinition* left,
                        MDefinition* right, MIRType type);

  MDefinition* foldIfZero(size_t operand) override {
    // 0 >>> x => 0
    if (operand == 0) {
      return getOperand(0);
    }

    return this;
  }

  bool bailoutsDisabled() const { return bailoutsDisabled_; }

  bool fallible() const;

  void computeRange(TempAllocator& alloc) override;
  void collectRangeInfoPreTrunc() override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MUrsh)
};

class MSignExtendInt32 : public MUnaryInstruction, public NoTypePolicy::Data {
 public:
  enum Mode { Byte, Half };

 private:
  Mode mode_;

  MSignExtendInt32(MDefinition* op, Mode mode)
      : MUnaryInstruction(classOpcode, op), mode_(mode) {
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(SignExtendInt32)
  TRIVIAL_NEW_WRAPPERS

  Mode mode() const { return mode_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!congruentIfOperandsEqual(ins)) {
      return false;
    }
    return ins->isSignExtendInt32() && ins->toSignExtendInt32()->mode_ == mode_;
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MSignExtendInt32)
};

class MSignExtendInt64 : public MUnaryInstruction, public NoTypePolicy::Data {
 public:
  enum Mode { Byte, Half, Word };

 private:
  Mode mode_;

  MSignExtendInt64(MDefinition* op, Mode mode)
      : MUnaryInstruction(classOpcode, op), mode_(mode) {
    setResultType(MIRType::Int64);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(SignExtendInt64)
  TRIVIAL_NEW_WRAPPERS

  Mode mode() const { return mode_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!congruentIfOperandsEqual(ins)) {
      return false;
    }
    return ins->isSignExtendInt64() && ins->toSignExtendInt64()->mode_ == mode_;
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MSignExtendInt64)
};

class MBinaryArithInstruction : public MBinaryInstruction,
                                public ArithPolicy::Data {
  // Implicit truncate flag is set by the truncate backward range analysis
  // optimization phase, and by wasm pre-processing. It is used in
  // NeedNegativeZeroCheck to check if the result of a multiplication needs to
  // produce -0 double value, and for avoiding overflow checks.

  // This optimization happens when the multiplication cannot be truncated
  // even if all uses are truncating its result, such as when the range
  // analysis detect a precision loss in the multiplication.
  TruncateKind implicitTruncate_;

  // Whether we must preserve NaN semantics, and in particular not fold
  // (x op id) or (id op x) to x, or replace a division by a multiply of the
  // exact reciprocal.
  bool mustPreserveNaN_;

 protected:
  MBinaryArithInstruction(Opcode op, MDefinition* left, MDefinition* right,
                          MIRType type)
      : MBinaryInstruction(op, left, right),
        implicitTruncate_(TruncateKind::NoTruncate),
        mustPreserveNaN_(false) {
    MOZ_ASSERT(IsNumberType(type));
    setResultType(type);
    setMovable();
  }

 public:
  void setMustPreserveNaN(bool b) { mustPreserveNaN_ = b; }
  bool mustPreserveNaN() const { return mustPreserveNaN_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
#ifdef JS_JITSPEW
  void printOpcode(GenericPrinter& out) const override;
#endif

  virtual double getIdentity() = 0;

  void setSpecialization(MIRType type) {
    MOZ_ASSERT(IsNumberType(type));
    setResultType(type);
  }

  virtual void trySpecializeFloat32(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    if (!binaryCongruentTo(ins)) {
      return false;
    }
    const auto* other = static_cast<const MBinaryArithInstruction*>(ins);
    return other->mustPreserveNaN_ == mustPreserveNaN_;
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool isTruncated() const {
    return implicitTruncate_ == TruncateKind::Truncate;
  }
  TruncateKind truncateKind() const { return implicitTruncate_; }
  void setTruncateKind(TruncateKind kind) {
    implicitTruncate_ = std::max(implicitTruncate_, kind);
  }
};

class MMinMax : public MBinaryInstruction, public ArithPolicy::Data {
  bool isMax_;

  MMinMax(MDefinition* left, MDefinition* right, MIRType type, bool isMax)
      : MBinaryInstruction(classOpcode, left, right), isMax_(isMax) {
    MOZ_ASSERT(IsNumberType(type));
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(MinMax)
  TRIVIAL_NEW_WRAPPERS

  static MMinMax* NewWasm(TempAllocator& alloc, MDefinition* left,
                          MDefinition* right, MIRType type, bool isMax) {
    return New(alloc, left, right, type, isMax);
  }

  bool isMax() const { return isMax_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!congruentIfOperandsEqual(ins)) {
      return false;
    }
    const MMinMax* other = ins->toMinMax();
    return other->isMax() == isMax();
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  MDefinition* foldsTo(TempAllocator& alloc) override;
  void computeRange(TempAllocator& alloc) override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;

  ALLOW_CLONE(MMinMax)
};

class MMinMaxArray : public MUnaryInstruction, public SingleObjectPolicy::Data {
  bool isMax_;

  MMinMaxArray(MDefinition* array, MIRType type, bool isMax)
      : MUnaryInstruction(classOpcode, array), isMax_(isMax) {
    MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Double);
    setResultType(type);

    // We can't DCE this, even if the result is unused, in case one of the
    // elements of the array is an object with a `valueOf` function that
    // must be called.
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(MinMaxArray)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, array))

  bool isMax() const { return isMax_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isMinMaxArray() || ins->toMinMaxArray()->isMax() != isMax()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::ObjectFields | AliasSet::Element);
  }
};

class MAbs : public MUnaryInstruction, public ArithPolicy::Data {
  bool implicitTruncate_;

  MAbs(MDefinition* num, MIRType type)
      : MUnaryInstruction(classOpcode, num), implicitTruncate_(false) {
    MOZ_ASSERT(IsNumberType(type));
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Abs)
  TRIVIAL_NEW_WRAPPERS

  static MAbs* NewWasm(TempAllocator& alloc, MDefinition* num, MIRType type) {
    auto* ins = new (alloc) MAbs(num, type);
    if (type == MIRType::Int32) {
      ins->implicitTruncate_ = true;
    }
    return ins;
  }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  bool fallible() const;

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;
  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MAbs)
};

class MClz : public MUnaryInstruction, public BitwisePolicy::Data {
  bool operandIsNeverZero_;

  explicit MClz(MDefinition* num, MIRType type)
      : MUnaryInstruction(classOpcode, num), operandIsNeverZero_(false) {
    MOZ_ASSERT(IsIntType(type));
    MOZ_ASSERT(IsNumberType(num->type()));
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Clz)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, num))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool operandIsNeverZero() const { return operandIsNeverZero_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  void computeRange(TempAllocator& alloc) override;
  void collectRangeInfoPreTrunc() override;
};

class MCtz : public MUnaryInstruction, public BitwisePolicy::Data {
  bool operandIsNeverZero_;

  explicit MCtz(MDefinition* num, MIRType type)
      : MUnaryInstruction(classOpcode, num), operandIsNeverZero_(false) {
    MOZ_ASSERT(IsIntType(type));
    MOZ_ASSERT(IsNumberType(num->type()));
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Ctz)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, num))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool operandIsNeverZero() const { return operandIsNeverZero_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  void computeRange(TempAllocator& alloc) override;
  void collectRangeInfoPreTrunc() override;
};

class MPopcnt : public MUnaryInstruction, public BitwisePolicy::Data {
  explicit MPopcnt(MDefinition* num, MIRType type)
      : MUnaryInstruction(classOpcode, num) {
    MOZ_ASSERT(IsNumberType(num->type()));
    MOZ_ASSERT(IsIntType(type));
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Popcnt)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, num))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  void computeRange(TempAllocator& alloc) override;
};

// Inline implementation of Math.sqrt().
class MSqrt : public MUnaryInstruction, public FloatingPointPolicy<0>::Data {
  MSqrt(MDefinition* num, MIRType type) : MUnaryInstruction(classOpcode, num) {
    setResultType(type);
    specialization_ = type;
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Sqrt)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;

  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MSqrt)
};

class MCopySign : public MBinaryInstruction, public NoTypePolicy::Data {
  MCopySign(MDefinition* lhs, MDefinition* rhs, MIRType type)
      : MBinaryInstruction(classOpcode, lhs, rhs) {
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(CopySign)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MCopySign)
};

// Inline implementation of Math.hypot().
class MHypot : public MVariadicInstruction, public AllDoublePolicy::Data {
  MHypot() : MVariadicInstruction(classOpcode) {
    setResultType(MIRType::Double);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Hypot)
  static MHypot* New(TempAllocator& alloc, const MDefinitionVector& vector);

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool possiblyCalls() const override { return true; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  bool canClone() const override { return true; }

  MInstruction* clone(TempAllocator& alloc,
                      const MDefinitionVector& inputs) const override {
    return MHypot::New(alloc, inputs);
  }
};

// Inline implementation of Math.pow().
//
// Supports the following three specializations:
//
// 1. MPow(FloatingPoint, FloatingPoint) -> Double
//   - The most general mode, calls js::ecmaPow.
//   - Never performs a bailout.
// 2. MPow(FloatingPoint, Int32) -> Double
//   - Optimization to call js::powi instead of js::ecmaPow.
//   - Never performs a bailout.
// 3. MPow(Int32, Int32) -> Int32
//   - Performs the complete exponentiation operation in assembly code.
//   - Bails out if the result doesn't fit in Int32.
class MPow : public MBinaryInstruction, public PowPolicy::Data {
  // If true, the result is guaranteed to never be negative zero, as long as the
  // power is a positive number.
  bool canBeNegativeZero_;

  MPow(MDefinition* input, MDefinition* power, MIRType specialization)
      : MBinaryInstruction(classOpcode, input, power) {
    MOZ_ASSERT(specialization == MIRType::Int32 ||
               specialization == MIRType::Double);
    setResultType(specialization);
    setMovable();

    // The result can't be negative zero if the base is an Int32 value.
    canBeNegativeZero_ = input->type() != MIRType::Int32;
  }

  // Helpers for `foldsTo`
  MDefinition* foldsConstant(TempAllocator& alloc);
  MDefinition* foldsConstantPower(TempAllocator& alloc);

  bool canBeNegativeZero() const { return canBeNegativeZero_; }

 public:
  INSTRUCTION_HEADER(Pow)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* input() const { return lhs(); }
  MDefinition* power() const { return rhs(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool possiblyCalls() const override { return type() != MIRType::Int32; }
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  ALLOW_CLONE(MPow)
};

// Inline implementation of Math.pow(x, 0.5), which subtly differs from
// Math.sqrt(x).
class MPowHalf : public MUnaryInstruction, public DoublePolicy<0>::Data {
  bool operandIsNeverNegativeInfinity_;
  bool operandIsNeverNegativeZero_;
  bool operandIsNeverNaN_;

  explicit MPowHalf(MDefinition* input)
      : MUnaryInstruction(classOpcode, input),
        operandIsNeverNegativeInfinity_(false),
        operandIsNeverNegativeZero_(false),
        operandIsNeverNaN_(false) {
    setResultType(MIRType::Double);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(PowHalf)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  bool operandIsNeverNegativeInfinity() const {
    return operandIsNeverNegativeInfinity_;
  }
  bool operandIsNeverNegativeZero() const {
    return operandIsNeverNegativeZero_;
  }
  bool operandIsNeverNaN() const { return operandIsNeverNaN_; }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void collectRangeInfoPreTrunc() override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MPowHalf)
};

class MSign : public MUnaryInstruction, public SignPolicy::Data {
 private:
  MSign(MDefinition* input, MIRType resultType)
      : MUnaryInstruction(classOpcode, input) {
    MOZ_ASSERT(IsNumberType(input->type()));
    MOZ_ASSERT(resultType == MIRType::Int32 || resultType == MIRType::Double);
    specialization_ = input->type();
    setResultType(resultType);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Sign)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  void computeRange(TempAllocator& alloc) override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MSign)
};

class MMathFunction : public MUnaryInstruction,
                      public FloatingPointPolicy<0>::Data {
  UnaryMathFunction function_;

  // A nullptr cache means this function will neither access nor update the
  // cache.
  MMathFunction(MDefinition* input, UnaryMathFunction function)
      : MUnaryInstruction(classOpcode, input), function_(function) {
    setResultType(MIRType::Double);
    specialization_ = MIRType::Double;
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(MathFunction)
  TRIVIAL_NEW_WRAPPERS

  UnaryMathFunction function() const { return function_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isMathFunction()) {
      return false;
    }
    if (ins->toMathFunction()->function() != function()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool possiblyCalls() const override { return true; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

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

  static const char* FunctionName(UnaryMathFunction function);

  bool isFloat32Commutative() const override;
  void trySpecializeFloat32(TempAllocator& alloc) override;

  void computeRange(TempAllocator& alloc) override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MMathFunction)
};

class MAdd : public MBinaryArithInstruction {
  MAdd(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryArithInstruction(classOpcode, left, right, type) {
    setCommutative();
  }

  MAdd(MDefinition* left, MDefinition* right, TruncateKind truncateKind)
      : MAdd(left, right, MIRType::Int32) {
    setTruncateKind(truncateKind);
  }

 public:
  INSTRUCTION_HEADER(Add)
  TRIVIAL_NEW_WRAPPERS

  static MAdd* NewWasm(TempAllocator& alloc, MDefinition* left,
                       MDefinition* right, MIRType type) {
    auto* ret = new (alloc) MAdd(left, right, type);
    if (type == MIRType::Int32) {
      ret->setTruncateKind(TruncateKind::Truncate);
    }
    return ret;
  }

  bool isFloat32Commutative() const override { return true; }

  double getIdentity() override { return 0; }

  bool fallible() const;
  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  TruncateKind operandTruncateKind(size_t index) const override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MAdd)
};

class MSub : public MBinaryArithInstruction {
  MSub(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryArithInstruction(classOpcode, left, right, type) {}

 public:
  INSTRUCTION_HEADER(Sub)
  TRIVIAL_NEW_WRAPPERS

  static MSub* NewWasm(TempAllocator& alloc, MDefinition* left,
                       MDefinition* right, MIRType type, bool mustPreserveNaN) {
    auto* ret = new (alloc) MSub(left, right, type);
    ret->setMustPreserveNaN(mustPreserveNaN);
    if (type == MIRType::Int32) {
      ret->setTruncateKind(TruncateKind::Truncate);
    }
    return ret;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  double getIdentity() override { return 0; }

  bool isFloat32Commutative() const override { return true; }

  bool fallible() const;
  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  TruncateKind operandTruncateKind(size_t index) const override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MSub)
};

class MMul : public MBinaryArithInstruction {
 public:
  enum Mode { Normal, Integer };

 private:
  // Annotation the result could be a negative zero
  // and we need to guard this during execution.
  bool canBeNegativeZero_;

  Mode mode_;

  MMul(MDefinition* left, MDefinition* right, MIRType type, Mode mode)
      : MBinaryArithInstruction(classOpcode, left, right, type),
        canBeNegativeZero_(true),
        mode_(mode) {
    setCommutative();
    if (mode == Integer) {
      // This implements the required behavior for Math.imul, which
      // can never fail and always truncates its output to int32.
      canBeNegativeZero_ = false;
      setTruncateKind(TruncateKind::Truncate);
    }
    MOZ_ASSERT_IF(mode != Integer, mode == Normal);
  }

 public:
  INSTRUCTION_HEADER(Mul)

  static MMul* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
                   MIRType type, Mode mode = Normal) {
    return new (alloc) MMul(left, right, type, mode);
  }
  static MMul* NewWasm(TempAllocator& alloc, MDefinition* left,
                       MDefinition* right, MIRType type, Mode mode,
                       bool mustPreserveNaN) {
    auto* ret = new (alloc) MMul(left, right, type, mode);
    ret->setMustPreserveNaN(mustPreserveNaN);
    return ret;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  void analyzeEdgeCasesForward() override;
  void analyzeEdgeCasesBackward() override;
  void collectRangeInfoPreTrunc() override;

  double getIdentity() override { return 1; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isMul()) {
      return false;
    }

    const MMul* mul = ins->toMul();
    if (canBeNegativeZero_ != mul->canBeNegativeZero()) {
      return false;
    }

    if (mode_ != mul->mode()) {
      return false;
    }

    if (mustPreserveNaN() != mul->mustPreserveNaN()) {
      return false;
    }

    return binaryCongruentTo(ins);
  }

  bool canOverflow() const;

  bool canBeNegativeZero() const { return canBeNegativeZero_; }
  void setCanBeNegativeZero(bool negativeZero) {
    canBeNegativeZero_ = negativeZero;
  }

  bool fallible() const { return canBeNegativeZero_ || canOverflow(); }

  bool isFloat32Commutative() const override { return true; }

  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  TruncateKind operandTruncateKind(size_t index) const override;

  Mode mode() const { return mode_; }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MMul)
};

class MDiv : public MBinaryArithInstruction {
  bool canBeNegativeZero_;
  bool canBeNegativeOverflow_;
  bool canBeDivideByZero_;
  bool canBeNegativeDividend_;
  bool unsigned_;  // If false, signedness will be derived from operands
  bool trapOnError_;
  wasm::BytecodeOffset bytecodeOffset_;

  MDiv(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryArithInstruction(classOpcode, left, right, type),
        canBeNegativeZero_(true),
        canBeNegativeOverflow_(true),
        canBeDivideByZero_(true),
        canBeNegativeDividend_(true),
        unsigned_(false),
        trapOnError_(false) {}

 public:
  INSTRUCTION_HEADER(Div)

  static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
                   MIRType type) {
    return new (alloc) MDiv(left, right, type);
  }
  static MDiv* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
                   MIRType type, bool unsignd, bool trapOnError = false,
                   wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset(),
                   bool mustPreserveNaN = false) {
    auto* div = new (alloc) MDiv(left, right, type);
    div->unsigned_ = unsignd;
    div->trapOnError_ = trapOnError;
    div->bytecodeOffset_ = bytecodeOffset;
    if (trapOnError) {
      div->setGuard();  // not removable because of possible side-effects.
      div->setNotMovable();
    }
    div->setMustPreserveNaN(mustPreserveNaN);
    if (type == MIRType::Int32) {
      div->setTruncateKind(TruncateKind::Truncate);
    }
    return div;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  void analyzeEdgeCasesForward() override;
  void analyzeEdgeCasesBackward() override;

  double getIdentity() override { MOZ_CRASH("not used"); }

  bool canBeNegativeZero() const { return canBeNegativeZero_; }
  void setCanBeNegativeZero(bool negativeZero) {
    canBeNegativeZero_ = negativeZero;
  }

  bool canBeNegativeOverflow() const { return canBeNegativeOverflow_; }

  bool canBeDivideByZero() const { return canBeDivideByZero_; }

  bool canBeNegativeDividend() const {
    // "Dividend" is an ambiguous concept for unsigned truncated
    // division, because of the truncation procedure:
    // ((x>>>0)/2)|0, for example, gets transformed in
    // MDiv::truncate into a node with lhs representing x (not
    // x>>>0) and rhs representing the constant 2; in other words,
    // the MIR node corresponds to "cast operands to unsigned and
    // divide" operation. In this case, is the dividend x or is it
    // x>>>0? In order to resolve such ambiguities, we disallow
    // the usage of this method for unsigned division.
    MOZ_ASSERT(!unsigned_);
    return canBeNegativeDividend_;
  }

  bool isUnsigned() const { return unsigned_; }

  bool isTruncatedIndirectly() const {
    return truncateKind() >= TruncateKind::IndirectTruncate;
  }

  bool canTruncateInfinities() const { return isTruncated(); }
  bool canTruncateRemainder() const { return isTruncated(); }
  bool canTruncateOverflow() const {
    return isTruncated() || isTruncatedIndirectly();
  }
  bool canTruncateNegativeZero() const {
    return isTruncated() || isTruncatedIndirectly();
  }

  bool trapOnError() const { return trapOnError_; }
  wasm::BytecodeOffset bytecodeOffset() const {
    MOZ_ASSERT(bytecodeOffset_.isValid());
    return bytecodeOffset_;
  }

  bool isFloat32Commutative() const override { return true; }

  void computeRange(TempAllocator& alloc) override;
  bool fallible() const;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  void collectRangeInfoPreTrunc() override;
  TruncateKind operandTruncateKind(size_t index) const override;

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!MBinaryArithInstruction::congruentTo(ins)) {
      return false;
    }
    const MDiv* other = ins->toDiv();
    MOZ_ASSERT(other->trapOnError() == trapOnError_);
    return unsigned_ == other->isUnsigned();
  }

  ALLOW_CLONE(MDiv)
};

class MWasmBuiltinDivI64 : public MAryInstruction<3>, public ArithPolicy::Data {
  bool canBeNegativeZero_;
  bool canBeNegativeOverflow_;
  bool canBeDivideByZero_;
  bool canBeNegativeDividend_;
  bool unsigned_;  // If false, signedness will be derived from operands
  bool trapOnError_;
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmBuiltinDivI64(MDefinition* left, MDefinition* right,
                     MDefinition* instance)
      : MAryInstruction(classOpcode),
        canBeNegativeZero_(true),
        canBeNegativeOverflow_(true),
        canBeDivideByZero_(true),
        canBeNegativeDividend_(true),
        unsigned_(false),
        trapOnError_(false) {
    initOperand(0, left);
    initOperand(1, right);
    initOperand(2, instance);

    setResultType(MIRType::Int64);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmBuiltinDivI64)

  NAMED_OPERANDS((0, lhs), (1, rhs), (2, instance))

  static MWasmBuiltinDivI64* New(
      TempAllocator& alloc, MDefinition* left, MDefinition* right,
      MDefinition* instance, bool unsignd, bool trapOnError = false,
      wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
    auto* wasm64Div = new (alloc) MWasmBuiltinDivI64(left, right, instance);
    wasm64Div->unsigned_ = unsignd;
    wasm64Div->trapOnError_ = trapOnError;
    wasm64Div->bytecodeOffset_ = bytecodeOffset;
    if (trapOnError) {
      wasm64Div->setGuard();  // not removable because of possible side-effects.
      wasm64Div->setNotMovable();
    }
    return wasm64Div;
  }

  bool canBeNegativeZero() const { return canBeNegativeZero_; }
  void setCanBeNegativeZero(bool negativeZero) {
    canBeNegativeZero_ = negativeZero;
  }

  bool canBeNegativeOverflow() const { return canBeNegativeOverflow_; }

  bool canBeDivideByZero() const { return canBeDivideByZero_; }

  bool canBeNegativeDividend() const {
    // "Dividend" is an ambiguous concept for unsigned truncated
    // division, because of the truncation procedure:
    // ((x>>>0)/2)|0, for example, gets transformed in
    // MWasmDiv::truncate into a node with lhs representing x (not
    // x>>>0) and rhs representing the constant 2; in other words,
    // the MIR node corresponds to "cast operands to unsigned and
    // divide" operation. In this case, is the dividend x or is it
    // x>>>0? In order to resolve such ambiguities, we disallow
    // the usage of this method for unsigned division.
    MOZ_ASSERT(!unsigned_);
    return canBeNegativeDividend_;
  }

  bool isUnsigned() const { return unsigned_; }

  bool trapOnError() const { return trapOnError_; }
  wasm::BytecodeOffset bytecodeOffset() const {
    MOZ_ASSERT(bytecodeOffset_.isValid());
    return bytecodeOffset_;
  }

  ALLOW_CLONE(MWasmBuiltinDivI64)
};

class MMod : public MBinaryArithInstruction {
  bool unsigned_;  // If false, signedness will be derived from operands
  bool canBeNegativeDividend_;
  bool canBePowerOfTwoDivisor_;
  bool canBeDivideByZero_;
  bool trapOnError_;
  wasm::BytecodeOffset bytecodeOffset_;

  MMod(MDefinition* left, MDefinition* right, MIRType type)
      : MBinaryArithInstruction(classOpcode, left, right, type),
        unsigned_(false),
        canBeNegativeDividend_(true),
        canBePowerOfTwoDivisor_(true),
        canBeDivideByZero_(true),
        trapOnError_(false) {}

 public:
  INSTRUCTION_HEADER(Mod)

  static MMod* New(TempAllocator& alloc, MDefinition* left, MDefinition* right,
                   MIRType type) {
    return new (alloc) MMod(left, right, type);
  }
  static MMod* New(
      TempAllocator& alloc, MDefinition* left, MDefinition* right, MIRType type,
      bool unsignd, bool trapOnError = false,
      wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
    auto* mod = new (alloc) MMod(left, right, type);
    mod->unsigned_ = unsignd;
    mod->trapOnError_ = trapOnError;
    mod->bytecodeOffset_ = bytecodeOffset;
    if (trapOnError) {
      mod->setGuard();  // not removable because of possible side-effects.
      mod->setNotMovable();
    }
    if (type == MIRType::Int32) {
      mod->setTruncateKind(TruncateKind::Truncate);
    }
    return mod;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  double getIdentity() override { MOZ_CRASH("not used"); }

  bool canBeNegativeDividend() const {
    MOZ_ASSERT(type() == MIRType::Int32 || type() == MIRType::Int64);
    MOZ_ASSERT(!unsigned_);
    return canBeNegativeDividend_;
  }

  bool canBeDivideByZero() const {
    MOZ_ASSERT(type() == MIRType::Int32 || type() == MIRType::Int64);
    return canBeDivideByZero_;
  }

  bool canBePowerOfTwoDivisor() const {
    MOZ_ASSERT(type() == MIRType::Int32);
    return canBePowerOfTwoDivisor_;
  }

  void analyzeEdgeCasesForward() override;

  bool isUnsigned() const { return unsigned_; }

  bool trapOnError() const { return trapOnError_; }
  wasm::BytecodeOffset bytecodeOffset() const {
    MOZ_ASSERT(bytecodeOffset_.isValid());
    return bytecodeOffset_;
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  bool fallible() const;

  void computeRange(TempAllocator& alloc) override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;
  void collectRangeInfoPreTrunc() override;
  TruncateKind operandTruncateKind(size_t index) const override;

  bool congruentTo(const MDefinition* ins) const override {
    return MBinaryArithInstruction::congruentTo(ins) &&
           unsigned_ == ins->toMod()->isUnsigned();
  }

  bool possiblyCalls() const override { return type() == MIRType::Double; }

  ALLOW_CLONE(MMod)
};

class MWasmBuiltinModD : public MAryInstruction<3>, public ArithPolicy::Data {
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmBuiltinModD(MDefinition* left, MDefinition* right, MDefinition* instance,
                   MIRType type)
      : MAryInstruction(classOpcode) {
    initOperand(0, left);
    initOperand(1, right);
    initOperand(2, instance);

    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmBuiltinModD)
  NAMED_OPERANDS((0, lhs), (1, rhs), (2, instance))

  static MWasmBuiltinModD* New(
      TempAllocator& alloc, MDefinition* left, MDefinition* right,
      MDefinition* instance, MIRType type,
      wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
    auto* wasmBuiltinModD =
        new (alloc) MWasmBuiltinModD(left, right, instance, type);
    wasmBuiltinModD->bytecodeOffset_ = bytecodeOffset;
    return wasmBuiltinModD;
  }

  wasm::BytecodeOffset bytecodeOffset() const {
    MOZ_ASSERT(bytecodeOffset_.isValid());
    return bytecodeOffset_;
  }

  ALLOW_CLONE(MWasmBuiltinModD)
};

class MWasmBuiltinModI64 : public MAryInstruction<3>, public ArithPolicy::Data {
  bool unsigned_;  // If false, signedness will be derived from operands
  bool canBeNegativeDividend_;
  bool canBeDivideByZero_;
  bool trapOnError_;
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmBuiltinModI64(MDefinition* left, MDefinition* right,
                     MDefinition* instance)
      : MAryInstruction(classOpcode),
        unsigned_(false),
        canBeNegativeDividend_(true),
        canBeDivideByZero_(true),
        trapOnError_(false) {
    initOperand(0, left);
    initOperand(1, right);
    initOperand(2, instance);

    setResultType(MIRType::Int64);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmBuiltinModI64)

  NAMED_OPERANDS((0, lhs), (1, rhs), (2, instance))

  static MWasmBuiltinModI64* New(
      TempAllocator& alloc, MDefinition* left, MDefinition* right,
      MDefinition* instance, bool unsignd, bool trapOnError = false,
      wasm::BytecodeOffset bytecodeOffset = wasm::BytecodeOffset()) {
    auto* mod = new (alloc) MWasmBuiltinModI64(left, right, instance);
    mod->unsigned_ = unsignd;
    mod->trapOnError_ = trapOnError;
    mod->bytecodeOffset_ = bytecodeOffset;
    if (trapOnError) {
      mod->setGuard();  // not removable because of possible side-effects.
      mod->setNotMovable();
    }
    return mod;
  }

  bool canBeNegativeDividend() const {
    MOZ_ASSERT(!unsigned_);
    return canBeNegativeDividend_;
  }

  bool canBeDivideByZero() const { return canBeDivideByZero_; }

  bool isUnsigned() const { return unsigned_; }

  bool trapOnError() const { return trapOnError_; }
  wasm::BytecodeOffset bytecodeOffset() const {
    MOZ_ASSERT(bytecodeOffset_.isValid());
    return bytecodeOffset_;
  }

  ALLOW_CLONE(MWasmBuiltinModI64)
};

class MBigIntBinaryArithInstruction : public MBinaryInstruction,
                                      public BigIntArithPolicy::Data {
 protected:
  MBigIntBinaryArithInstruction(Opcode op, MDefinition* left,
                                MDefinition* right)
      : MBinaryInstruction(op, left, right) {
    setResultType(MIRType::BigInt);
    setMovable();
  }

 public:
  bool congruentTo(const MDefinition* ins) const override {
    return binaryCongruentTo(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MBigIntAdd : public MBigIntBinaryArithInstruction {
  MBigIntAdd(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    setCommutative();

    // Don't guard this instruction even though adding two BigInts can throw
    // JSMSG_BIGINT_TOO_LARGE. This matches the behavior when adding too large
    // strings in MConcat.
  }

 public:
  INSTRUCTION_HEADER(BigIntAdd)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntAdd)
};

class MBigIntSub : public MBigIntBinaryArithInstruction {
  MBigIntSub(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntSub)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntSub)
};

class MBigIntMul : public MBigIntBinaryArithInstruction {
  MBigIntMul(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    setCommutative();

    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntMul)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntMul)
};

class MBigIntDiv : public MBigIntBinaryArithInstruction {
  bool canBeDivideByZero_;

  MBigIntDiv(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    MOZ_ASSERT(right->type() == MIRType::BigInt);
    canBeDivideByZero_ =
        !right->isConstant() || right->toConstant()->toBigInt()->isZero();

    // Throws when the divisor is zero.
    if (canBeDivideByZero_) {
      setGuard();
      setNotMovable();
    }
  }

 public:
  INSTRUCTION_HEADER(BigIntDiv)
  TRIVIAL_NEW_WRAPPERS

  bool canBeDivideByZero() const { return canBeDivideByZero_; }

  AliasSet getAliasSet() const override {
    if (canBeDivideByZero()) {
      return AliasSet::Store(AliasSet::ExceptionState);
    }
    return AliasSet::None();
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return !canBeDivideByZero(); }

  ALLOW_CLONE(MBigIntDiv)
};

class MBigIntMod : public MBigIntBinaryArithInstruction {
  bool canBeDivideByZero_;

  MBigIntMod(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    MOZ_ASSERT(right->type() == MIRType::BigInt);
    canBeDivideByZero_ =
        !right->isConstant() || right->toConstant()->toBigInt()->isZero();

    // Throws when the divisor is zero.
    if (canBeDivideByZero_) {
      setGuard();
      setNotMovable();
    }
  }

 public:
  INSTRUCTION_HEADER(BigIntMod)
  TRIVIAL_NEW_WRAPPERS

  bool canBeDivideByZero() const { return canBeDivideByZero_; }

  AliasSet getAliasSet() const override {
    if (canBeDivideByZero()) {
      return AliasSet::Store(AliasSet::ExceptionState);
    }
    return AliasSet::None();
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return !canBeDivideByZero(); }

  ALLOW_CLONE(MBigIntMod)
};

class MBigIntPow : public MBigIntBinaryArithInstruction {
  bool canBeNegativeExponent_;

  MBigIntPow(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    MOZ_ASSERT(right->type() == MIRType::BigInt);
    canBeNegativeExponent_ =
        !right->isConstant() || right->toConstant()->toBigInt()->isNegative();

    // Throws when the exponent is negative.
    if (canBeNegativeExponent_) {
      setGuard();
      setNotMovable();
    }
  }

 public:
  INSTRUCTION_HEADER(BigIntPow)
  TRIVIAL_NEW_WRAPPERS

  bool canBeNegativeExponent() const { return canBeNegativeExponent_; }

  AliasSet getAliasSet() const override {
    if (canBeNegativeExponent()) {
      return AliasSet::Store(AliasSet::ExceptionState);
    }
    return AliasSet::None();
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return !canBeNegativeExponent(); }

  ALLOW_CLONE(MBigIntPow)
};

class MBigIntBitAnd : public MBigIntBinaryArithInstruction {
  MBigIntBitAnd(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    setCommutative();

    // We don't need to guard this instruction because it can only fail on OOM.
  }

 public:
  INSTRUCTION_HEADER(BigIntBitAnd)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntBitAnd)
};

class MBigIntBitOr : public MBigIntBinaryArithInstruction {
  MBigIntBitOr(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    setCommutative();

    // We don't need to guard this instruction because it can only fail on OOM.
  }

 public:
  INSTRUCTION_HEADER(BigIntBitOr)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntBitOr)
};

class MBigIntBitXor : public MBigIntBinaryArithInstruction {
  MBigIntBitXor(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    setCommutative();

    // We don't need to guard this instruction because it can only fail on OOM.
  }

 public:
  INSTRUCTION_HEADER(BigIntBitXor)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntBitXor)
};

class MBigIntLsh : public MBigIntBinaryArithInstruction {
  MBigIntLsh(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntLsh)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntLsh)
};

class MBigIntRsh : public MBigIntBinaryArithInstruction {
  MBigIntRsh(MDefinition* left, MDefinition* right)
      : MBigIntBinaryArithInstruction(classOpcode, left, right) {
    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntRsh)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntRsh)
};

class MBigIntUnaryArithInstruction : public MUnaryInstruction,
                                     public BigIntArithPolicy::Data {
 protected:
  MBigIntUnaryArithInstruction(Opcode op, MDefinition* input)
      : MUnaryInstruction(op, input) {
    setResultType(MIRType::BigInt);
    setMovable();
  }

 public:
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MBigIntIncrement : public MBigIntUnaryArithInstruction {
  explicit MBigIntIncrement(MDefinition* input)
      : MBigIntUnaryArithInstruction(classOpcode, input) {
    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntIncrement)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntIncrement)
};

class MBigIntDecrement : public MBigIntUnaryArithInstruction {
  explicit MBigIntDecrement(MDefinition* input)
      : MBigIntUnaryArithInstruction(classOpcode, input) {
    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntDecrement)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntDecrement)
};

class MBigIntNegate : public MBigIntUnaryArithInstruction {
  explicit MBigIntNegate(MDefinition* input)
      : MBigIntUnaryArithInstruction(classOpcode, input) {
    // We don't need to guard this instruction because it can only fail on OOM.
  }

 public:
  INSTRUCTION_HEADER(BigIntNegate)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntNegate)
};

class MBigIntBitNot : public MBigIntUnaryArithInstruction {
  explicit MBigIntBitNot(MDefinition* input)
      : MBigIntUnaryArithInstruction(classOpcode, input) {
    // See MBigIntAdd for why we don't guard this instruction.
  }

 public:
  INSTRUCTION_HEADER(BigIntBitNot)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MBigIntBitNot)
};

class MConcat : public MBinaryInstruction,
                public MixPolicy<ConvertToStringPolicy<0>,
                                 ConvertToStringPolicy<1>>::Data {
  MConcat(MDefinition* left, MDefinition* right)
      : MBinaryInstruction(classOpcode, left, right) {
    // At least one input should be definitely string
    MOZ_ASSERT(left->type() == MIRType::String ||
               right->type() == MIRType::String);

    setMovable();
    setResultType(MIRType::String);
  }

 public:
  INSTRUCTION_HEADER(Concat)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MConcat)
};

class MStringConvertCase : public MUnaryInstruction,
                           public StringPolicy<0>::Data {
 public:
  enum Mode { LowerCase, UpperCase };

 private:
  Mode mode_;

  MStringConvertCase(MDefinition* string, Mode mode)
      : MUnaryInstruction(classOpcode, string), mode_(mode) {
    setResultType(MIRType::String);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(StringConvertCase)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, string))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toStringConvertCase()->mode() == mode();
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool possiblyCalls() const override { return true; }
  Mode mode() const { return mode_; }
};

// This is a 3 state flag used by FlagPhiInputsAsImplicitlyUsed to record and
// propagate the information about the consumers of a Phi instruction. This is
// then used to set ImplicitlyUsed flags on the inputs of such Phi instructions.
enum class PhiUsage : uint8_t { Unknown, Unused, Used };

using PhiVector = Vector<MPhi*, 4, JitAllocPolicy>;

class MPhi final : public MDefinition,
                   public InlineListNode<MPhi>,
                   public NoTypePolicy::Data {
  using InputVector = js::Vector<MUse, 2, JitAllocPolicy>;
  InputVector inputs_;

  TruncateKind truncateKind_;
  bool triedToSpecialize_;
  bool isIterator_;
  bool canProduceFloat32_;
  bool canConsumeFloat32_;
  // Record the state of the data flow before any mutation made to the control
  // flow, such that removing branches is properly accounted for.
  PhiUsage usageAnalysis_;

 protected:
  MUse* getUseFor(size_t index) override {
    MOZ_ASSERT(index < numOperands());
    return &inputs_[index];
  }
  const MUse* getUseFor(size_t index) const override { return &inputs_[index]; }

 public:
  INSTRUCTION_HEADER_WITHOUT_TYPEPOLICY(Phi)
  virtual const TypePolicy* typePolicy();
  virtual MIRType typePolicySpecialization();

  MPhi(TempAllocator& alloc, MIRType resultType)
      : MDefinition(classOpcode),
        inputs_(alloc),
        truncateKind_(TruncateKind::NoTruncate),
        triedToSpecialize_(false),
        isIterator_(false),
        canProduceFloat32_(false),
        canConsumeFloat32_(false),
        usageAnalysis_(PhiUsage::Unknown) {
    setResultType(resultType);
  }

  static MPhi* New(TempAllocator& alloc, MIRType resultType = MIRType::Value) {
    return new (alloc) MPhi(alloc, resultType);
  }
  static MPhi* New(TempAllocator::Fallible alloc,
                   MIRType resultType = MIRType::Value) {
    return new (alloc) MPhi(alloc.alloc, resultType);
  }

  void removeOperand(size_t index);
  void removeAllOperands();

  MDefinition* getOperand(size_t index) const override {
    return inputs_[index].producer();
  }
  size_t numOperands() const override { return inputs_.length(); }
  size_t indexOf(const MUse* u) const final {
    MOZ_ASSERT(u >= &inputs_[0]);
    MOZ_ASSERT(u <= &inputs_[numOperands() - 1]);
    return u - &inputs_[0];
  }
  void replaceOperand(size_t index, MDefinition* operand) final {
    inputs_[index].replaceProducer(operand);
  }
  bool triedToSpecialize() const { return triedToSpecialize_; }
  void specialize(MIRType type) {
    triedToSpecialize_ = true;
    setResultType(type);
  }

#ifdef DEBUG
  // Assert that this is a phi in a loop header with a unique predecessor and
  // a unique backedge.
  void assertLoopPhi() const;
#else
  void assertLoopPhi() const {}
#endif

  // Assuming this phi is in a loop header with a unique loop entry, return
  // the phi operand along the loop entry.
  MDefinition* getLoopPredecessorOperand() const;

  // Assuming this phi is in a loop header with a unique loop entry, return
  // the phi operand along the loop backedge.
  MDefinition* getLoopBackedgeOperand() const;

  // Whether this phi's type already includes information for def.
  bool typeIncludes(MDefinition* def);

  // Mark all phis in |iterators|, and the phis they flow into, as having
  // implicit uses.
  [[nodiscard]] static bool markIteratorPhis(const PhiVector& iterators);

  // Initializes the operands vector to the given capacity,
  // permitting use of addInput() instead of addInputSlow().
  [[nodiscard]] bool reserveLength(size_t length) {
    return inputs_.reserve(length);
  }

  // Use only if capacity has been reserved by reserveLength
  void addInput(MDefinition* ins) {
    MOZ_ASSERT_IF(type() != MIRType::Value, ins->type() == type());
    inputs_.infallibleEmplaceBack(ins, this);
  }

  // Appends a new input to the input vector. May perform reallocation.
  // Prefer reserveLength() and addInput() instead, where possible.
  [[nodiscard]] bool addInputSlow(MDefinition* ins) {
    MOZ_ASSERT_IF(type() != MIRType::Value, ins->type() == type());
    return inputs_.emplaceBack(ins, this);
  }

  // Appends a new input to the input vector. Infallible because
  // we know the inputs fits in the vector's inline storage.
  void addInlineInput(MDefinition* ins) {
    MOZ_ASSERT(inputs_.length() < InputVector::InlineLength);
    MOZ_ALWAYS_TRUE(addInputSlow(ins));
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  MDefinition* foldsTernary(TempAllocator& alloc);

  bool congruentTo(const MDefinition* ins) const override;

  // Mark this phi-node as having replaced all uses of |other|, as during GVN.
  // For use when GVN eliminates phis which are not equivalent to one another.
  void updateForReplacement(MPhi* other);

  bool isIterator() const { return isIterator_; }
  void setIterator() { isIterator_ = true; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;

  MDefinition* operandIfRedundant();

  bool canProduceFloat32() const override { return canProduceFloat32_; }

  void setCanProduceFloat32(bool can) { canProduceFloat32_ = can; }

  bool canConsumeFloat32(MUse* use) const override {
    return canConsumeFloat32_;
  }

  void setCanConsumeFloat32(bool can) { canConsumeFloat32_ = can; }

  TruncateKind operandTruncateKind(size_t index) const override;
  bool canTruncate() const override;
  void truncate(TruncateKind kind) override;

  PhiUsage getUsageAnalysis() const { return usageAnalysis_; }
  void setUsageAnalysis(PhiUsage pu) {
    MOZ_ASSERT(usageAnalysis_ == PhiUsage::Unknown);
    usageAnalysis_ = pu;
    MOZ_ASSERT(usageAnalysis_ != PhiUsage::Unknown);
  }
};

// The goal of a Beta node is to split a def at a conditionally taken
// branch, so that uses dominated by it have a different name.
class MBeta : public MUnaryInstruction, public NoTypePolicy::Data {
 private:
  // This is the range induced by a comparison and branch in a preceding
  // block. Note that this does not reflect any range constraints from
  // the input value itself, so this value may differ from the range()
  // range after it is computed.
  const Range* comparison_;

  MBeta(MDefinition* val, const Range* comp)
      : MUnaryInstruction(classOpcode, val), comparison_(comp) {
    setResultType(val->type());
  }

 public:
  INSTRUCTION_HEADER(Beta)
  TRIVIAL_NEW_WRAPPERS

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

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;
};

// If input evaluates to false (i.e. it's NaN, 0 or -0), 0 is returned, else the
// input is returned
class MNaNToZero : public MUnaryInstruction, public DoublePolicy<0>::Data {
  bool operandIsNeverNaN_;
  bool operandIsNeverNegativeZero_;

  explicit MNaNToZero(MDefinition* input)
      : MUnaryInstruction(classOpcode, input),
        operandIsNeverNaN_(false),
        operandIsNeverNegativeZero_(false) {
    setResultType(MIRType::Double);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(NaNToZero)
  TRIVIAL_NEW_WRAPPERS

  bool operandIsNeverNaN() const { return operandIsNeverNaN_; }

  bool operandIsNeverNegativeZero() const {
    return operandIsNeverNegativeZero_;
  }

  void collectRangeInfoPreTrunc() override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  void computeRange(TempAllocator& alloc) override;

  bool writeRecoverData(CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MNaNToZero)
};

// MIR representation of a Value on the OSR BaselineFrame.
// The Value is indexed off of OsrFrameReg.
class MOsrValue : public MUnaryInstruction, public NoTypePolicy::Data {
 private:
  ptrdiff_t frameOffset_;

  MOsrValue(MOsrEntry* entry, ptrdiff_t frameOffset)
      : MUnaryInstruction(classOpcode, entry), frameOffset_(frameOffset) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(OsrValue)
  TRIVIAL_NEW_WRAPPERS

  ptrdiff_t frameOffset() const { return frameOffset_; }

  MOsrEntry* entry() { return getOperand(0)->toOsrEntry(); }

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// MIR representation of a JSObject scope chain pointer on the OSR
// BaselineFrame. The pointer is indexed off of OsrFrameReg.
class MOsrEnvironmentChain : public MUnaryInstruction,
                             public NoTypePolicy::Data {
 private:
  explicit MOsrEnvironmentChain(MOsrEntry* entry)
      : MUnaryInstruction(classOpcode, entry) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(OsrEnvironmentChain)
  TRIVIAL_NEW_WRAPPERS

  MOsrEntry* entry() { return getOperand(0)->toOsrEntry(); }
};

// MIR representation of a JSObject ArgumentsObject pointer on the OSR
// BaselineFrame. The pointer is indexed off of OsrFrameReg.
class MOsrArgumentsObject : public MUnaryInstruction,
                            public NoTypePolicy::Data {
 private:
  explicit MOsrArgumentsObject(MOsrEntry* entry)
      : MUnaryInstruction(classOpcode, entry) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(OsrArgumentsObject)
  TRIVIAL_NEW_WRAPPERS

  MOsrEntry* entry() { return getOperand(0)->toOsrEntry(); }
};

// MIR representation of the return value on the OSR BaselineFrame.
// The Value is indexed off of OsrFrameReg.
class MOsrReturnValue : public MUnaryInstruction, public NoTypePolicy::Data {
 private:
  explicit MOsrReturnValue(MOsrEntry* entry)
      : MUnaryInstruction(classOpcode, entry) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(OsrReturnValue)
  TRIVIAL_NEW_WRAPPERS

  MOsrEntry* entry() { return getOperand(0)->toOsrEntry(); }
};

class MBinaryCache : public MBinaryInstruction,
                     public MixPolicy<BoxPolicy<0>, BoxPolicy<1>>::Data {
  explicit MBinaryCache(MDefinition* left, MDefinition* right, MIRType resType)
      : MBinaryInstruction(classOpcode, left, right) {
    setResultType(resType);
  }

 public:
  INSTRUCTION_HEADER(BinaryCache)
  TRIVIAL_NEW_WRAPPERS
};

// Check whether we need to fire the interrupt handler (in wasm code).
class MWasmInterruptCheck : public MUnaryInstruction,
                            public NoTypePolicy::Data {
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmInterruptCheck(MDefinition* instance,
                      wasm::BytecodeOffset bytecodeOffset)
      : MUnaryInstruction(classOpcode, instance),
        bytecodeOffset_(bytecodeOffset) {
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(WasmInterruptCheck)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance))

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};

// Directly jumps to the indicated trap, leaving Wasm code and reporting a
// runtime error.

class MWasmTrap : public MAryControlInstruction<0, 0>,
                  public NoTypePolicy::Data {
  wasm::Trap trap_;
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MWasmTrap(wasm::Trap trap, wasm::BytecodeOffset bytecodeOffset)
      : MAryControlInstruction(classOpcode),
        trap_(trap),
        bytecodeOffset_(bytecodeOffset) {}

 public:
  INSTRUCTION_HEADER(WasmTrap)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  wasm::Trap trap() const { return trap_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};

// Checks if a value is JS_UNINITIALIZED_LEXICAL, bailout out if so, leaving
// it to baseline to throw at the correct pc.
class MLexicalCheck : public MUnaryInstruction, public BoxPolicy<0>::Data {
  explicit MLexicalCheck(MDefinition* input)
      : MUnaryInstruction(classOpcode, input) {
    setResultType(MIRType::Value);
    setMovable();
    setGuard();

    // If this instruction bails out, we will set a flag to prevent
    // lexical checks in this script from being moved.
    setBailoutKind(BailoutKind::UninitializedLexical);
  }

 public:
  INSTRUCTION_HEADER(LexicalCheck)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
};

// Unconditionally throw a known error number.
class MThrowMsg : public MNullaryInstruction {
  const ThrowMsgKind throwMsgKind_;

  explicit MThrowMsg(ThrowMsgKind throwMsgKind)
      : MNullaryInstruction(classOpcode), throwMsgKind_(throwMsgKind) {
    setGuard();
    setResultType(MIRType::None);
  }

 public:
  INSTRUCTION_HEADER(ThrowMsg)
  TRIVIAL_NEW_WRAPPERS

  ThrowMsgKind throwMsgKind() const { return throwMsgKind_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::ExceptionState);
  }
};

class MGetFirstDollarIndex : public MUnaryInstruction,
                             public StringPolicy<0>::Data {
  explicit MGetFirstDollarIndex(MDefinition* str)
      : MUnaryInstruction(classOpcode, str) {
    setResultType(MIRType::Int32);

    // Codegen assumes string length > 0. Don't allow LICM to move this
    // before the .length > 1 check in RegExpReplace in RegExp.js.
    MOZ_ASSERT(!isMovable());
  }

 public:
  INSTRUCTION_HEADER(GetFirstDollarIndex)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, str))

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  MDefinition* foldsTo(TempAllocator& alloc) override;
};

class MStringReplace : public MTernaryInstruction,
                       public MixPolicy<StringPolicy<0>, StringPolicy<1>,
                                        StringPolicy<2>>::Data {
 private:
  bool isFlatReplacement_;

  MStringReplace(MDefinition* string, MDefinition* pattern,
                 MDefinition* replacement)
      : MTernaryInstruction(classOpcode, string, pattern, replacement),
        isFlatReplacement_(false) {
    setMovable();
    setResultType(MIRType::String);
  }

 public:
  INSTRUCTION_HEADER(StringReplace)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, string), (1, pattern), (2, replacement))

  void setFlatReplacement() {
    MOZ_ASSERT(!isFlatReplacement_);
    isFlatReplacement_ = true;
  }

  bool isFlatReplacement() const { return isFlatReplacement_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isStringReplace()) {
      return false;
    }
    if (isFlatReplacement_ != ins->toStringReplace()->isFlatReplacement()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override {
    if (isFlatReplacement_) {
      MOZ_ASSERT(!pattern()->isRegExp());
      return true;
    }
    return false;
  }

  bool possiblyCalls() const override { return true; }
};

class MLambda : public MBinaryInstruction, public SingleObjectPolicy::Data {
  MLambda(MDefinition* envChain, MConstant* cst)
      : MBinaryInstruction(classOpcode, envChain, cst) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(Lambda)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, environmentChain))

  MConstant* functionOperand() const { return getOperand(1)->toConstant(); }
  JSFunction* templateFunction() const {
    return &functionOperand()->toObject().as<JSFunction>();
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MFunctionWithProto : public MTernaryInstruction,
                           public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1>,
                                            ObjectPolicy<2>>::Data {
  CompilerFunction fun_;

  MFunctionWithProto(MDefinition* envChain, MDefinition* prototype,
                     MConstant* cst)
      : MTernaryInstruction(classOpcode, envChain, prototype, cst),
        fun_(&cst->toObject().as<JSFunction>()) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(FunctionWithProto)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, environmentChain), (1, prototype))

  MConstant* functionOperand() const { return getOperand(2)->toConstant(); }
  JSFunction* function() const { return fun_; }
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  bool possiblyCalls() const override { return true; }
};

class MGetNextEntryForIterator
    : public MBinaryInstruction,
      public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1>>::Data {
 public:
  enum Mode { Map, Set };

 private:
  Mode mode_;

  explicit MGetNextEntryForIterator(MDefinition* iter, MDefinition* result,
                                    Mode mode)
      : MBinaryInstruction(classOpcode, iter, result), mode_(mode) {
    setResultType(MIRType::Boolean);
  }

 public:
  INSTRUCTION_HEADER(GetNextEntryForIterator)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, iter), (1, result))

  Mode mode() const { return mode_; }
};

// Convert a Double into an IntPtr value for accessing a TypedArray or DataView
// element. If the input is non-finite, not an integer, negative, or outside the
// IntPtr range, either bails out or produces a value which is known to trigger
// an out-of-bounds access (this depends on the supportOOB flag).
class MGuardNumberToIntPtrIndex : public MUnaryInstruction,
                                  public DoublePolicy<0>::Data {
  // If true, produce an out-of-bounds index for non-IntPtr doubles instead of
  // bailing out.
  const bool supportOOB_;

  MGuardNumberToIntPtrIndex(MDefinition* def, bool supportOOB)
      : MUnaryInstruction(classOpcode, def), supportOOB_(supportOOB) {
    MOZ_ASSERT(def->type() == MIRType::Double);
    setResultType(MIRType::IntPtr);
    setMovable();
    if (!supportOOB) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(GuardNumberToIntPtrIndex)
  TRIVIAL_NEW_WRAPPERS

  bool supportOOB() const { return supportOOB_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardNumberToIntPtrIndex()) {
      return false;
    }
    if (ins->toGuardNumberToIntPtrIndex()->supportOOB() != supportOOB()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  ALLOW_CLONE(MGuardNumberToIntPtrIndex)
};

// Perform !-operation
class MNot : public MUnaryInstruction, public TestPolicy::Data {
  bool operandIsNeverNaN_;
  TypeDataList observedTypes_;

  explicit MNot(MDefinition* input)
      : MUnaryInstruction(classOpcode, input), operandIsNeverNaN_(false) {
    setResultType(MIRType::Boolean);
    setMovable();
  }

 public:
  static MNot* NewInt32(TempAllocator& alloc, MDefinition* input) {
    MOZ_ASSERT(input->type() == MIRType::Int32 ||
               input->type() == MIRType::Int64);
    auto* ins = new (alloc) MNot(input);
    ins->setResultType(MIRType::Int32);
    return ins;
  }

  INSTRUCTION_HEADER(Not)
  TRIVIAL_NEW_WRAPPERS

  void setObservedTypes(const TypeDataList& observed) {
    observedTypes_ = observed;
  }
  const TypeDataList& observedTypes() const { return observedTypes_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool operandIsNeverNaN() const { return operandIsNeverNaN_; }

  virtual AliasSet getAliasSet() const override { return AliasSet::None(); }
  void collectRangeInfoPreTrunc() override;

  void trySpecializeFloat32(TempAllocator& alloc) override;
  bool isFloat32Commutative() const override { return true; }
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

// Bailout if index + minimum < 0 or index + maximum >= length. The length used
// in a bounds check must not be negative, or the wrong result may be computed
// (unsigned comparisons may be used).
class MBoundsCheck
    : public MBinaryInstruction,
      public MixPolicy<Int32OrIntPtrPolicy<0>, Int32OrIntPtrPolicy<1>>::Data {
  // Range over which to perform the bounds check, may be modified by GVN.
  int32_t minimum_;
  int32_t maximum_;
  bool fallible_;

  MBoundsCheck(MDefinition* index, MDefinition* length)
      : MBinaryInstruction(classOpcode, index, length),
        minimum_(0),
        maximum_(0),
        fallible_(true) {
    setGuard();
    setMovable();
    MOZ_ASSERT(index->type() == MIRType::Int32 ||
               index->type() == MIRType::IntPtr);
    MOZ_ASSERT(index->type() == length->type());

    // Returns the checked index.
    setResultType(index->type());
  }

 public:
  INSTRUCTION_HEADER(BoundsCheck)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, index), (1, length))

  int32_t minimum() const { return minimum_; }
  void setMinimum(int32_t n) {
    MOZ_ASSERT(fallible_);
    minimum_ = n;
  }
  int32_t maximum() const { return maximum_; }
  void setMaximum(int32_t n) {
    MOZ_ASSERT(fallible_);
    maximum_ = n;
  }
  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isBoundsCheck()) {
      return false;
    }
    const MBoundsCheck* other = ins->toBoundsCheck();
    if (minimum() != other->minimum() || maximum() != other->maximum()) {
      return false;
    }
    if (fallible() != other->fallible()) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }
  virtual AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;
  bool fallible() const { return fallible_; }
  void collectRangeInfoPreTrunc() override;

  ALLOW_CLONE(MBoundsCheck)
};

// Bailout if index < minimum.
class MBoundsCheckLower : public MUnaryInstruction,
                          public UnboxedInt32Policy<0>::Data {
  int32_t minimum_;
  bool fallible_;

  explicit MBoundsCheckLower(MDefinition* index)
      : MUnaryInstruction(classOpcode, index), minimum_(0), fallible_(true) {
    setGuard();
    setMovable();
    MOZ_ASSERT(index->type() == MIRType::Int32);
  }

 public:
  INSTRUCTION_HEADER(BoundsCheckLower)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, index))

  int32_t minimum() const { return minimum_; }
  void setMinimum(int32_t n) { minimum_ = n; }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool fallible() const { return fallible_; }
  void collectRangeInfoPreTrunc() override;
};

class MSpectreMaskIndex
    : public MBinaryInstruction,
      public MixPolicy<Int32OrIntPtrPolicy<0>, Int32OrIntPtrPolicy<1>>::Data {
  MSpectreMaskIndex(MDefinition* index, MDefinition* length)
      : MBinaryInstruction(classOpcode, index, length) {
    // Note: this instruction does not need setGuard(): if there are no uses
    // it's fine for DCE to eliminate this instruction.
    setMovable();
    MOZ_ASSERT(index->type() == MIRType::Int32 ||
               index->type() == MIRType::IntPtr);
    MOZ_ASSERT(index->type() == length->type());

    // Returns the masked index.
    setResultType(index->type());
  }

 public:
  INSTRUCTION_HEADER(SpectreMaskIndex)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, index), (1, length))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  virtual AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;

  ALLOW_CLONE(MSpectreMaskIndex)
};

// Load a value from a dense array's element vector. Bails out if the element is
// a hole.
class MLoadElement : public MBinaryInstruction, public NoTypePolicy::Data {
  MLoadElement(MDefinition* elements, MDefinition* index)
      : MBinaryInstruction(classOpcode, elements, index) {
    // Uses may be optimized away based on this instruction's result
    // type. This means it's invalid to DCE this instruction, as we
    // have to invalidate when we read a hole.
    setGuard();
    setResultType(MIRType::Value);
    setMovable();
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
  }

 public:
  INSTRUCTION_HEADER(LoadElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasType mightAlias(const MDefinition* store) const override;
  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::Element);
  }

  ALLOW_CLONE(MLoadElement)
};

class MLoadElementAndUnbox : public MBinaryInstruction,
                             public NoTypePolicy::Data {
  MUnbox::Mode mode_;

  MLoadElementAndUnbox(MDefinition* elements, MDefinition* index,
                       MUnbox::Mode mode, MIRType type)
      : MBinaryInstruction(classOpcode, elements, index), mode_(mode) {
    setResultType(type);
    setMovable();
    if (mode_ == MUnbox::Fallible) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(LoadElementAndUnbox)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index))

  MUnbox::Mode mode() const { return mode_; }
  bool fallible() const { return mode_ != MUnbox::Infallible; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadElementAndUnbox() ||
        mode() != ins->toLoadElementAndUnbox()->mode()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::Element);
  }

  ALLOW_CLONE(MLoadElementAndUnbox);
};

// Load a value from the elements vector of a native object. If the index is
// out-of-bounds, or the indexed slot has a hole, undefined is returned instead.
class MLoadElementHole : public MTernaryInstruction, public NoTypePolicy::Data {
  bool needsNegativeIntCheck_ = true;

  MLoadElementHole(MDefinition* elements, MDefinition* index,
                   MDefinition* initLength)
      : MTernaryInstruction(classOpcode, elements, index, initLength) {
    setResultType(MIRType::Value);
    setMovable();

    // Set the guard flag to make sure we bail when we see a negative
    // index. We can clear this flag (and needsNegativeIntCheck_) in
    // collectRangeInfoPreTrunc.
    setGuard();

    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
    MOZ_ASSERT(initLength->type() == MIRType::Int32);
  }

 public:
  INSTRUCTION_HEADER(LoadElementHole)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, initLength))

  bool needsNegativeIntCheck() const { return needsNegativeIntCheck_; }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadElementHole()) {
      return false;
    }
    const MLoadElementHole* other = ins->toLoadElementHole();
    if (needsNegativeIntCheck() != other->needsNegativeIntCheck()) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::Element);
  }
  void collectRangeInfoPreTrunc() override;

  ALLOW_CLONE(MLoadElementHole)
};

// Store a value to a dense array slots vector.
class MStoreElement : public MTernaryInstruction,
                      public NoFloatPolicy<2>::Data {
  bool needsHoleCheck_;
  bool needsBarrier_;

  MStoreElement(MDefinition* elements, MDefinition* index, MDefinition* value,
                bool needsHoleCheck, bool needsBarrier)
      : MTernaryInstruction(classOpcode, elements, index, value) {
    needsHoleCheck_ = needsHoleCheck;
    needsBarrier_ = needsBarrier;
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
    MOZ_ASSERT(value->type() != MIRType::MagicHole);
  }

 public:
  INSTRUCTION_HEADER(StoreElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, value))

  static MStoreElement* NewUnbarriered(TempAllocator& alloc,
                                       MDefinition* elements,
                                       MDefinition* index, MDefinition* value,
                                       bool needsHoleCheck) {
    return new (alloc)
        MStoreElement(elements, index, value, needsHoleCheck, false);
  }

  static MStoreElement* NewBarriered(TempAllocator& alloc,
                                     MDefinition* elements, MDefinition* index,
                                     MDefinition* value, bool needsHoleCheck) {
    return new (alloc)
        MStoreElement(elements, index, value, needsHoleCheck, true);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::Element);
  }
  bool needsBarrier() const { return needsBarrier_; }
  bool needsHoleCheck() const { return needsHoleCheck_; }
  bool fallible() const { return needsHoleCheck(); }

  ALLOW_CLONE(MStoreElement)
};

// Stores MagicValue(JS_ELEMENTS_HOLE) and marks the elements as non-packed.
class MStoreHoleValueElement : public MBinaryInstruction,
                               public NoTypePolicy::Data {
  MStoreHoleValueElement(MDefinition* elements, MDefinition* index)
      : MBinaryInstruction(classOpcode, elements, index) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
  }

 public:
  INSTRUCTION_HEADER(StoreHoleValueElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index))

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::Element | AliasSet::ObjectFields);
  }

  ALLOW_CLONE(MStoreHoleValueElement)
};

// Like MStoreElement, but also supports index == initialized length. The
// downside is that we cannot hoist the elements vector and bounds check, since
// this instruction may update the (initialized) length and reallocate the
// elements vector.
class MStoreElementHole
    : public MQuaternaryInstruction,
      public MixPolicy<SingleObjectPolicy, NoFloatPolicy<3>>::Data {
  MStoreElementHole(MDefinition* object, MDefinition* elements,
                    MDefinition* index, MDefinition* value)
      : MQuaternaryInstruction(classOpcode, object, elements, index, value) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
    MOZ_ASSERT(value->type() != MIRType::MagicHole);
  }

 public:
  INSTRUCTION_HEADER(StoreElementHole)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, elements), (2, index), (3, value))

  AliasSet getAliasSet() const override {
    // StoreElementHole can update the initialized length, the array length
    // or reallocate obj->elements.
    return AliasSet::Store(AliasSet::ObjectFields | AliasSet::Element);
  }

  ALLOW_CLONE(MStoreElementHole)
};

// Array.prototype.pop or Array.prototype.shift on a dense array.
class MArrayPopShift : public MUnaryInstruction,
                       public SingleObjectPolicy::Data {
 public:
  enum Mode { Pop, Shift };

 private:
  Mode mode_;

  MArrayPopShift(MDefinition* object, Mode mode)
      : MUnaryInstruction(classOpcode, object), mode_(mode) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(ArrayPopShift)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  bool mode() const { return mode_; }
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::ObjectFields | AliasSet::Element);
  }

  ALLOW_CLONE(MArrayPopShift)
};

// All barriered operations - MCompareExchangeTypedArrayElement,
// MExchangeTypedArrayElement, and MAtomicTypedArrayElementBinop, as
// well as MLoadUnboxedScalar and MStoreUnboxedScalar when they are
// marked as requiring a memory barrer - have the following
// attributes:
//
// - Not movable
// - Not removable
// - Not congruent with any other instruction
// - Effectful (they alias every TypedArray store)
//
// The intended effect of those constraints is to prevent all loads
// and stores preceding the barriered operation from being moved to
// after the barriered operation, and vice versa, and to prevent the
// barriered operation from being removed or hoisted.

enum MemoryBarrierRequirement {
  DoesNotRequireMemoryBarrier,
  DoesRequireMemoryBarrier
};

// Also see comments at MMemoryBarrierRequirement, above.

// Load an unboxed scalar value from an array buffer view or other object.
class MLoadUnboxedScalar : public MBinaryInstruction,
                           public NoTypePolicy::Data {
  int32_t offsetAdjustment_ = 0;
  Scalar::Type storageType_;
  bool requiresBarrier_;

  MLoadUnboxedScalar(
      MDefinition* elements, MDefinition* index, Scalar::Type storageType,
      MemoryBarrierRequirement requiresBarrier = DoesNotRequireMemoryBarrier)
      : MBinaryInstruction(classOpcode, elements, index),
        storageType_(storageType),
        requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier) {
    setResultType(MIRType::Value);
    if (requiresBarrier_) {
      setGuard();  // Not removable or movable
    } else {
      setMovable();
    }
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(storageType >= 0 && storageType < Scalar::MaxTypedArrayViewType);
  }

 public:
  INSTRUCTION_HEADER(LoadUnboxedScalar)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index))

  Scalar::Type storageType() const { return storageType_; }
  bool fallible() const {
    // Bailout if the result does not fit in an int32.
    return storageType_ == Scalar::Uint32 && type() == MIRType::Int32;
  }
  bool requiresMemoryBarrier() const { return requiresBarrier_; }
  int32_t offsetAdjustment() const { return offsetAdjustment_; }
  void setOffsetAdjustment(int32_t offsetAdjustment) {
    offsetAdjustment_ = offsetAdjustment;
  }
  AliasSet getAliasSet() const override {
    // When a barrier is needed make the instruction effectful by
    // giving it a "store" effect.
    if (requiresBarrier_) {
      return AliasSet::Store(AliasSet::UnboxedElement);
    }
    return AliasSet::Load(AliasSet::UnboxedElement);
  }

  bool congruentTo(const MDefinition* ins) const override {
    if (requiresBarrier_) {
      return false;
    }
    if (!ins->isLoadUnboxedScalar()) {
      return false;
    }
    const MLoadUnboxedScalar* other = ins->toLoadUnboxedScalar();
    if (storageType_ != other->storageType_) {
      return false;
    }
    if (offsetAdjustment() != other->offsetAdjustment()) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }

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

  void computeRange(TempAllocator& alloc) override;

  bool canProduceFloat32() const override {
    return storageType_ == Scalar::Float32;
  }

  ALLOW_CLONE(MLoadUnboxedScalar)
};

// Load an unboxed scalar value from a dataview object.
class MLoadDataViewElement : public MTernaryInstruction,
                             public NoTypePolicy::Data {
  Scalar::Type storageType_;

  MLoadDataViewElement(MDefinition* elements, MDefinition* index,
                       MDefinition* littleEndian, Scalar::Type storageType)
      : MTernaryInstruction(classOpcode, elements, index, littleEndian),
        storageType_(storageType) {
    setResultType(MIRType::Value);
    setMovable();
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(littleEndian->type() == MIRType::Boolean);
    MOZ_ASSERT(storageType >= 0 && storageType < Scalar::MaxTypedArrayViewType);
    MOZ_ASSERT(Scalar::byteSize(storageType) > 1);
  }

 public:
  INSTRUCTION_HEADER(LoadDataViewElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, littleEndian))

  Scalar::Type storageType() const { return storageType_; }
  bool fallible() const {
    // Bailout if the result does not fit in an int32.
    return storageType_ == Scalar::Uint32 && type() == MIRType::Int32;
  }
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::UnboxedElement);
  }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadDataViewElement()) {
      return false;
    }
    const MLoadDataViewElement* other = ins->toLoadDataViewElement();
    if (storageType_ != other->storageType_) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }

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

  void computeRange(TempAllocator& alloc) override;

  bool canProduceFloat32() const override {
    return storageType_ == Scalar::Float32;
  }

  ALLOW_CLONE(MLoadDataViewElement)
};

// Load a value from a typed array. Out-of-bounds accesses are handled in-line.
class MLoadTypedArrayElementHole : public MBinaryInstruction,
                                   public SingleObjectPolicy::Data {
  Scalar::Type arrayType_;
  bool forceDouble_;

  MLoadTypedArrayElementHole(MDefinition* object, MDefinition* index,
                             Scalar::Type arrayType, bool forceDouble)
      : MBinaryInstruction(classOpcode, object, index),
        arrayType_(arrayType),
        forceDouble_(forceDouble) {
    setResultType(MIRType::Value);
    setMovable();
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(arrayType >= 0 && arrayType < Scalar::MaxTypedArrayViewType);
  }

 public:
  INSTRUCTION_HEADER(LoadTypedArrayElementHole)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, index))

  Scalar::Type arrayType() const { return arrayType_; }
  bool forceDouble() const { return forceDouble_; }
  bool fallible() const {
    return arrayType_ == Scalar::Uint32 && !forceDouble_;
  }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadTypedArrayElementHole()) {
      return false;
    }
    const MLoadTypedArrayElementHole* other =
        ins->toLoadTypedArrayElementHole();
    if (arrayType() != other->arrayType()) {
      return false;
    }
    if (forceDouble() != other->forceDouble()) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::UnboxedElement | AliasSet::ObjectFields |
                          AliasSet::ArrayBufferViewLengthOrOffset);
  }
  bool canProduceFloat32() const override {
    return arrayType_ == Scalar::Float32;
  }

  ALLOW_CLONE(MLoadTypedArrayElementHole)
};

// Base class for MIR ops that write unboxed scalar values.
class StoreUnboxedScalarBase {
  Scalar::Type writeType_;

 protected:
  explicit StoreUnboxedScalarBase(Scalar::Type writeType)
      : writeType_(writeType) {
    MOZ_ASSERT(isIntegerWrite() || isFloatWrite() || isBigIntWrite());
  }

 public:
  Scalar::Type writeType() const { return writeType_; }
  bool isByteWrite() const {
    return writeType_ == Scalar::Int8 || writeType_ == Scalar::Uint8 ||
           writeType_ == Scalar::Uint8Clamped;
  }
  bool isIntegerWrite() const {
    return isByteWrite() || writeType_ == Scalar::Int16 ||
           writeType_ == Scalar::Uint16 || writeType_ == Scalar::Int32 ||
           writeType_ == Scalar::Uint32;
  }
  bool isFloatWrite() const {
    return writeType_ == Scalar::Float32 || writeType_ == Scalar::Float64;
  }
  bool isBigIntWrite() const { return Scalar::isBigIntType(writeType_); }
};

// Store an unboxed scalar value to an array buffer view or other object.
class MStoreUnboxedScalar : public MTernaryInstruction,
                            public StoreUnboxedScalarBase,
                            public StoreUnboxedScalarPolicy::Data {
  bool requiresBarrier_;

  MStoreUnboxedScalar(
      MDefinition* elements, MDefinition* index, MDefinition* value,
      Scalar::Type storageType,
      MemoryBarrierRequirement requiresBarrier = DoesNotRequireMemoryBarrier)
      : MTernaryInstruction(classOpcode, elements, index, value),
        StoreUnboxedScalarBase(storageType),
        requiresBarrier_(requiresBarrier == DoesRequireMemoryBarrier) {
    if (requiresBarrier_) {
      setGuard();  // Not removable or movable
    }
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(storageType >= 0 && storageType < Scalar::MaxTypedArrayViewType);
  }

 public:
  INSTRUCTION_HEADER(StoreUnboxedScalar)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, value))

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::UnboxedElement);
  }
  bool requiresMemoryBarrier() const { return requiresBarrier_; }
  TruncateKind operandTruncateKind(size_t index) const override;

  bool canConsumeFloat32(MUse* use) const override {
    return use == getUseFor(2) && writeType() == Scalar::Float32;
  }

  ALLOW_CLONE(MStoreUnboxedScalar)
};

// Store an unboxed scalar value to a dataview object.
class MStoreDataViewElement : public MQuaternaryInstruction,
                              public StoreUnboxedScalarBase,
                              public StoreDataViewElementPolicy::Data {
  MStoreDataViewElement(MDefinition* elements, MDefinition* index,
                        MDefinition* value, MDefinition* littleEndian,
                        Scalar::Type storageType)
      : MQuaternaryInstruction(classOpcode, elements, index, value,
                               littleEndian),
        StoreUnboxedScalarBase(storageType) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(storageType >= 0 && storageType < Scalar::MaxTypedArrayViewType);
    MOZ_ASSERT(Scalar::byteSize(storageType) > 1);
  }

 public:
  INSTRUCTION_HEADER(StoreDataViewElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, value), (3, littleEndian))

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::UnboxedElement);
  }
  TruncateKind operandTruncateKind(size_t index) const override;

  bool canConsumeFloat32(MUse* use) const override {
    return use == getUseFor(2) && writeType() == Scalar::Float32;
  }

  ALLOW_CLONE(MStoreDataViewElement)
};

class MStoreTypedArrayElementHole : public MQuaternaryInstruction,
                                    public StoreUnboxedScalarBase,
                                    public StoreTypedArrayHolePolicy::Data {
  MStoreTypedArrayElementHole(MDefinition* elements, MDefinition* length,
                              MDefinition* index, MDefinition* value,
                              Scalar::Type arrayType)
      : MQuaternaryInstruction(classOpcode, elements, length, index, value),
        StoreUnboxedScalarBase(arrayType) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(length->type() == MIRType::IntPtr);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(arrayType >= 0 && arrayType < Scalar::MaxTypedArrayViewType);
  }

 public:
  INSTRUCTION_HEADER(StoreTypedArrayElementHole)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, length), (2, index), (3, value))

  Scalar::Type arrayType() const { return writeType(); }
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::UnboxedElement);
  }
  TruncateKind operandTruncateKind(size_t index) const override;

  bool canConsumeFloat32(MUse* use) const override {
    return use == getUseFor(3) && arrayType() == Scalar::Float32;
  }

  ALLOW_CLONE(MStoreTypedArrayElementHole)
};

// Compute an "effective address", i.e., a compound computation of the form:
//   base + index * scale + displacement
class MEffectiveAddress : public MBinaryInstruction, public NoTypePolicy::Data {
  MEffectiveAddress(MDefinition* base, MDefinition* index, Scale scale,
                    int32_t displacement)
      : MBinaryInstruction(classOpcode, base, index),
        scale_(scale),
        displacement_(displacement) {
    MOZ_ASSERT(base->type() == MIRType::Int32);
    MOZ_ASSERT(index->type() == MIRType::Int32);
    setMovable();
    setResultType(MIRType::Int32);
  }

  Scale scale_;
  int32_t displacement_;

 public:
  INSTRUCTION_HEADER(EffectiveAddress)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* base() const { return lhs(); }
  MDefinition* index() const { return rhs(); }
  Scale scale() const { return scale_; }
  int32_t displacement() const { return displacement_; }

  ALLOW_CLONE(MEffectiveAddress)
};

// Clamp input to range [0, 255] for Uint8ClampedArray.
class MClampToUint8 : public MUnaryInstruction, public ClampPolicy::Data {
  explicit MClampToUint8(MDefinition* input)
      : MUnaryInstruction(classOpcode, input) {
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(ClampToUint8)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  void computeRange(TempAllocator& alloc) override;

  ALLOW_CLONE(MClampToUint8)
};

class MLoadFixedSlot : public MUnaryInstruction,
                       public SingleObjectPolicy::Data {
  size_t slot_;

 protected:
  MLoadFixedSlot(MDefinition* obj, size_t slot)
      : MUnaryInstruction(classOpcode, obj), slot_(slot) {
    setResultType(MIRType::Value);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(LoadFixedSlot)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  size_t slot() const { return slot_; }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadFixedSlot()) {
      return false;
    }
    if (slot() != ins->toLoadFixedSlot()->slot()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::FixedSlot);
  }

  AliasType mightAlias(const MDefinition* store) const override;

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

  ALLOW_CLONE(MLoadFixedSlot)
};

class MLoadFixedSlotAndUnbox : public MUnaryInstruction,
                               public SingleObjectPolicy::Data {
  size_t slot_;
  MUnbox::Mode mode_;

  MLoadFixedSlotAndUnbox(MDefinition* obj, size_t slot, MUnbox::Mode mode,
                         MIRType type)
      : MUnaryInstruction(classOpcode, obj), slot_(slot), mode_(mode) {
    setResultType(type);
    setMovable();
    if (mode_ == MUnbox::Fallible) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(LoadFixedSlotAndUnbox)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  size_t slot() const { return slot_; }
  MUnbox::Mode mode() const { return mode_; }
  bool fallible() const { return mode_ != MUnbox::Infallible; }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadFixedSlotAndUnbox() ||
        slot() != ins->toLoadFixedSlotAndUnbox()->slot() ||
        mode() != ins->toLoadFixedSlotAndUnbox()->mode()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::FixedSlot);
  }

  AliasType mightAlias(const MDefinition* store) const override;

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

  ALLOW_CLONE(MLoadFixedSlotAndUnbox);
};

class MLoadDynamicSlotAndUnbox : public MUnaryInstruction,
                                 public NoTypePolicy::Data {
  size_t slot_;
  MUnbox::Mode mode_;

  MLoadDynamicSlotAndUnbox(MDefinition* slots, size_t slot, MUnbox::Mode mode,
                           MIRType type)
      : MUnaryInstruction(classOpcode, slots), slot_(slot), mode_(mode) {
    setResultType(type);
    setMovable();
    if (mode_ == MUnbox::Fallible) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(LoadDynamicSlotAndUnbox)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, slots))

  size_t slot() const { return slot_; }
  MUnbox::Mode mode() const { return mode_; }
  bool fallible() const { return mode_ != MUnbox::Infallible; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadDynamicSlotAndUnbox() ||
        slot() != ins->toLoadDynamicSlotAndUnbox()->slot() ||
        mode() != ins->toLoadDynamicSlotAndUnbox()->mode()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::DynamicSlot);
  }

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

  ALLOW_CLONE(MLoadDynamicSlotAndUnbox);
};

class MStoreFixedSlot
    : public MBinaryInstruction,
      public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1>>::Data {
  bool needsBarrier_;
  size_t slot_;

  MStoreFixedSlot(MDefinition* obj, MDefinition* rval, size_t slot,
                  bool barrier)
      : MBinaryInstruction(classOpcode, obj, rval),
        needsBarrier_(barrier),
        slot_(slot) {}

 public:
  INSTRUCTION_HEADER(StoreFixedSlot)
  NAMED_OPERANDS((0, object), (1, value))

  static MStoreFixedSlot* NewUnbarriered(TempAllocator& alloc, MDefinition* obj,
                                         size_t slot, MDefinition* rval) {
    return new (alloc) MStoreFixedSlot(obj, rval, slot, false);
  }
  static MStoreFixedSlot* NewBarriered(TempAllocator& alloc, MDefinition* obj,
                                       size_t slot, MDefinition* rval) {
    return new (alloc) MStoreFixedSlot(obj, rval, slot, true);
  }

  size_t slot() const { return slot_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::FixedSlot);
  }
  bool needsBarrier() const { return needsBarrier_; }
  void setNeedsBarrier(bool needsBarrier = true) {
    needsBarrier_ = needsBarrier;
  }

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

  ALLOW_CLONE(MStoreFixedSlot)
};

class MGetPropertyCache : public MBinaryInstruction,
                          public MixPolicy<BoxExceptPolicy<0, MIRType::Object>,
                                           CacheIdPolicy<1>>::Data {
  MGetPropertyCache(MDefinition* obj, MDefinition* id)
      : MBinaryInstruction(classOpcode, obj, id) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(GetPropertyCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value), (1, idval))
};

class MGetPropSuperCache
    : public MTernaryInstruction,
      public MixPolicy<ObjectPolicy<0>, BoxExceptPolicy<1, MIRType::Object>,
                       CacheIdPolicy<2>>::Data {
  MGetPropSuperCache(MDefinition* obj, MDefinition* receiver, MDefinition* id)
      : MTernaryInstruction(classOpcode, obj, receiver, id) {
    setResultType(MIRType::Value);
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(GetPropSuperCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, receiver), (2, idval))
};

// Guard the object's proto is |expected|.
class MGuardProto : public MBinaryInstruction, public SingleObjectPolicy::Data {
  MGuardProto(MDefinition* obj, MDefinition* expected)
      : MBinaryInstruction(classOpcode, obj, expected) {
    MOZ_ASSERT(expected->isConstant() || expected->isNurseryObject());
    setGuard();
    setMovable();
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(GuardProto)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, expected))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::ObjectFields);
  }
  AliasType mightAlias(const MDefinition* def) const override {
    // These instructions never modify the [[Prototype]].
    if (def->isAddAndStoreSlot() || def->isAllocateAndStoreSlot()) {
      return AliasType::NoAlias;
    }
    return AliasType::MayAlias;
  }
};

// Guard the object has no proto.
class MGuardNullProto : public MUnaryInstruction,
                        public SingleObjectPolicy::Data {
  explicit MGuardNullProto(MDefinition* obj)
      : MUnaryInstruction(classOpcode, obj) {
    setGuard();
    setMovable();
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(GuardNullProto)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::ObjectFields);
  }
  AliasType mightAlias(const MDefinition* def) const override {
    // These instructions never modify the [[Prototype]].
    if (def->isAddAndStoreSlot() || def->isAllocateAndStoreSlot()) {
      return AliasType::NoAlias;
    }
    return AliasType::MayAlias;
  }
};

// Guard on a specific Value.
class MGuardValue : public MUnaryInstruction, public BoxInputsPolicy::Data {
  Value expected_;

  MGuardValue(MDefinition* val, const Value& expected)
      : MUnaryInstruction(classOpcode, val), expected_(expected) {
    setGuard();
    setMovable();
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(GuardValue)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value))

  Value expected() const { return expected_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardValue()) {
      return false;
    }
    if (expected() != ins->toGuardValue()->expected()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Guard on function flags
class MGuardFunctionFlags : public MUnaryInstruction,
                            public SingleObjectPolicy::Data {
  // At least one of the expected flags must be set, but not necessarily all
  // expected flags.
  uint16_t expectedFlags_;

  // None of the unexpected flags must be set.
  uint16_t unexpectedFlags_;

  explicit MGuardFunctionFlags(MDefinition* fun, uint16_t expectedFlags,
                               uint16_t unexpectedFlags)
      : MUnaryInstruction(classOpcode, fun),
        expectedFlags_(expectedFlags),
        unexpectedFlags_(unexpectedFlags) {
    MOZ_ASSERT((expectedFlags & unexpectedFlags) == 0,
               "Can't guard inconsistent flags");
    MOZ_ASSERT((expectedFlags | unexpectedFlags) != 0,
               "Can't guard zero flags");
    setGuard();
    setMovable();
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(GuardFunctionFlags)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, function))

  uint16_t expectedFlags() const { return expectedFlags_; };
  uint16_t unexpectedFlags() const { return unexpectedFlags_; };

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardFunctionFlags()) {
      return false;
    }
    if (expectedFlags() != ins->toGuardFunctionFlags()->expectedFlags()) {
      return false;
    }
    if (unexpectedFlags() != ins->toGuardFunctionFlags()->unexpectedFlags()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::ObjectFields);
  }
};

// Guard on an object's identity, inclusively or exclusively.
class MGuardObjectIdentity : public MBinaryInstruction,
                             public SingleObjectPolicy::Data {
  bool bailOnEquality_;

  MGuardObjectIdentity(MDefinition* obj, MDefinition* expected,
                       bool bailOnEquality)
      : MBinaryInstruction(classOpcode, obj, expected),
        bailOnEquality_(bailOnEquality) {
    setGuard();
    setMovable();
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(GuardObjectIdentity)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, expected))

  bool bailOnEquality() const { return bailOnEquality_; }
  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardObjectIdentity()) {
      return false;
    }
    if (bailOnEquality() != ins->toGuardObjectIdentity()->bailOnEquality()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

// Guard on a specific JSFunction. Used instead of MGuardObjectIdentity,
// so we can store some metadata related to the expected function.
class MGuardSpecificFunction : public MBinaryInstruction,
                               public SingleObjectPolicy::Data {
  uint16_t nargs_;
  FunctionFlags flags_;

  MGuardSpecificFunction(MDefinition* obj, MDefinition* expected,
                         uint16_t nargs, FunctionFlags flags)
      : MBinaryInstruction(classOpcode, obj, expected),
        nargs_(nargs),
        flags_(flags) {
    MOZ_ASSERT(expected->isConstant() || expected->isNurseryObject());
    setGuard();
    setMovable();
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(GuardSpecificFunction)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, function), (1, expected))

  uint16_t nargs() const { return nargs_; }
  FunctionFlags flags() const { return flags_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardSpecificFunction()) {
      return false;
    }

    auto* other = ins->toGuardSpecificFunction();
    if (nargs() != other->nargs() ||
        flags().toRaw() != other->flags().toRaw()) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MGuardSpecificSymbol : public MUnaryInstruction,
                             public SymbolPolicy<0>::Data {
  CompilerGCPointer<JS::Symbol*> expected_;

  MGuardSpecificSymbol(MDefinition* symbol, JS::Symbol* expected)
      : MUnaryInstruction(classOpcode, symbol), expected_(expected) {
    setGuard();
    setMovable();
    setResultType(MIRType::Symbol);
  }

 public:
  INSTRUCTION_HEADER(GuardSpecificSymbol)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, symbol))

  JS::Symbol* expected() const { return expected_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardSpecificSymbol()) {
      return false;
    }
    if (expected() != ins->toGuardSpecificSymbol()->expected()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MGuardTagNotEqual
    : public MBinaryInstruction,
      public MixPolicy<UnboxedInt32Policy<0>, UnboxedInt32Policy<1>>::Data {
  MGuardTagNotEqual(MDefinition* left, MDefinition* right)
      : MBinaryInstruction(classOpcode, left, right) {
    setGuard();
    setMovable();
    setCommutative();
  }

 public:
  INSTRUCTION_HEADER(GuardTagNotEqual)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return binaryCongruentTo(ins);
  }
};

// Load from vp[slot] (slots that are not inline in an object).
class MLoadDynamicSlot : public MUnaryInstruction, public NoTypePolicy::Data {
  uint32_t slot_;

  MLoadDynamicSlot(MDefinition* slots, uint32_t slot)
      : MUnaryInstruction(classOpcode, slots), slot_(slot) {
    setResultType(MIRType::Value);
    setMovable();
    MOZ_ASSERT(slots->type() == MIRType::Slots);
  }

 public:
  INSTRUCTION_HEADER(LoadDynamicSlot)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, slots))

  uint32_t slot() const { return slot_; }

  HashNumber valueHash() const override;
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadDynamicSlot()) {
      return false;
    }
    if (slot() != ins->toLoadDynamicSlot()->slot()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override {
    MOZ_ASSERT(slots()->type() == MIRType::Slots);
    return AliasSet::Load(AliasSet::DynamicSlot);
  }
  AliasType mightAlias(const MDefinition* store) const override;

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

  ALLOW_CLONE(MLoadDynamicSlot)
};

class MAddAndStoreSlot
    : public MBinaryInstruction,
      public MixPolicy<SingleObjectPolicy, BoxPolicy<1>>::Data {
 public:
  enum class Kind {
    FixedSlot,
    DynamicSlot,
  };

 private:
  Kind kind_;
  uint32_t slotOffset_;
  CompilerShape shape_;

  MAddAndStoreSlot(MDefinition* obj, MDefinition* value, Kind kind,
                   uint32_t slotOffset, Shape* shape)
      : MBinaryInstruction(classOpcode, obj, value),
        kind_(kind),
        slotOffset_(slotOffset),
        shape_(shape) {}

 public:
  INSTRUCTION_HEADER(AddAndStoreSlot)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, value))

  Kind kind() const { return kind_; }
  uint32_t slotOffset() const { return slotOffset_; }
  Shape* shape() const { return shape_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::ObjectFields |
                           (kind() == Kind::FixedSlot ? AliasSet::FixedSlot
                                                      : AliasSet::DynamicSlot));
  }
};

// Store to vp[slot] (slots that are not inline in an object).
class MStoreDynamicSlot : public MBinaryInstruction,
                          public NoFloatPolicy<1>::Data {
  uint32_t slot_;
  bool needsBarrier_;

  MStoreDynamicSlot(MDefinition* slots, uint32_t slot, MDefinition* value,
                    bool barrier)
      : MBinaryInstruction(classOpcode, slots, value),
        slot_(slot),
        needsBarrier_(barrier) {
    MOZ_ASSERT(slots->type() == MIRType::Slots);
  }

 public:
  INSTRUCTION_HEADER(StoreDynamicSlot)
  NAMED_OPERANDS((0, slots), (1, value))

  static MStoreDynamicSlot* NewUnbarriered(TempAllocator& alloc,
                                           MDefinition* slots, uint32_t slot,
                                           MDefinition* value) {
    return new (alloc) MStoreDynamicSlot(slots, slot, value, false);
  }
  static MStoreDynamicSlot* NewBarriered(TempAllocator& alloc,
                                         MDefinition* slots, uint32_t slot,
                                         MDefinition* value) {
    return new (alloc) MStoreDynamicSlot(slots, slot, value, true);
  }

  uint32_t slot() const { return slot_; }
  bool needsBarrier() const { return needsBarrier_; }
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::DynamicSlot);
  }

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

  ALLOW_CLONE(MStoreDynamicSlot)
};

class MSetPropertyCache : public MTernaryInstruction,
                          public MixPolicy<SingleObjectPolicy, CacheIdPolicy<1>,
                                           NoFloatPolicy<2>>::Data {
  bool strict_ : 1;

  MSetPropertyCache(MDefinition* obj, MDefinition* id, MDefinition* value,
                    bool strict)
      : MTernaryInstruction(classOpcode, obj, id, value), strict_(strict) {}

 public:
  INSTRUCTION_HEADER(SetPropertyCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, idval), (2, value))

  bool strict() const { return strict_; }
};

class MMegamorphicSetElement : public MTernaryInstruction,
                               public MegamorphicSetElementPolicy::Data {
  bool strict_;

  MMegamorphicSetElement(MDefinition* object, MDefinition* index,
                         MDefinition* value, bool strict)
      : MTernaryInstruction(classOpcode, object, index, value),
        strict_(strict) {}

 public:
  INSTRUCTION_HEADER(MegamorphicSetElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, index), (2, value))

  bool strict() const { return strict_; }

  bool possiblyCalls() const override { return true; }
};

class MSetDOMProperty : public MBinaryInstruction,
                        public MixPolicy<ObjectPolicy<0>, BoxPolicy<1>>::Data {
  const JSJitSetterOp func_;
  Realm* setterRealm_;
  DOMObjectKind objectKind_;

  MSetDOMProperty(const JSJitSetterOp func, DOMObjectKind objectKind,
                  Realm* setterRealm, MDefinition* obj, MDefinition* val)
      : MBinaryInstruction(classOpcode, obj, val),
        func_(func),
        setterRealm_(setterRealm),
        objectKind_(objectKind) {}

 public:
  INSTRUCTION_HEADER(SetDOMProperty)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, value))

  JSJitSetterOp fun() const { return func_; }
  Realm* setterRealm() const { return setterRealm_; }
  DOMObjectKind objectKind() const { return objectKind_; }

  bool possiblyCalls() const override { return true; }
};

class MGetDOMPropertyBase : public MVariadicInstruction,
                            public ObjectPolicy<0>::Data {
  const JSJitInfo* info_;

 protected:
  MGetDOMPropertyBase(Opcode op, const JSJitInfo* jitinfo)
      : MVariadicInstruction(op), info_(jitinfo) {
    MOZ_ASSERT(jitinfo);
    MOZ_ASSERT(jitinfo->type() == JSJitInfo::Getter);

    // We are movable iff the jitinfo says we can be.
    if (isDomMovable()) {
      MOZ_ASSERT(jitinfo->aliasSet() != JSJitInfo::AliasEverything);
      setMovable();
    } else {
      // If we're not movable, that means we shouldn't be DCEd either,
      // because we might throw an exception when called, and getting rid
      // of that is observable.
      setGuard();
    }

    setResultType(MIRType::Value);
  }

  const JSJitInfo* info() const { return info_; }

  [[nodiscard]] bool init(TempAllocator& alloc, MDefinition* obj,
                          MDefinition* guard, MDefinition* globalGuard) {
    MOZ_ASSERT(obj);
    // guard can be null.
    // globalGuard can be null.
    size_t operandCount = 1;
    if (guard) {
      ++operandCount;
    }
    if (globalGuard) {
      ++operandCount;
    }
    if (!MVariadicInstruction::init(alloc, operandCount)) {
      return false;
    }
    initOperand(0, obj);

    size_t operandIndex = 1;
    // Pin the guard, if we have one as an operand if we want to hoist later.
    if (guard) {
      initOperand(operandIndex++, guard);
    }

    // And the same for the global guard, if we have one.
    if (globalGuard) {
      initOperand(operandIndex, globalGuard);
    }

    return true;
  }

 public:
  NAMED_OPERANDS((0, object))

  JSJitGetterOp fun() const { return info_->getter; }
  bool isInfallible() const { return info_->isInfallible; }
  bool isDomMovable() const { return info_->isMovable; }
  JSJitInfo::AliasSet domAliasSet() const { return info_->aliasSet(); }
  size_t domMemberSlotIndex() const {
    MOZ_ASSERT(info_->isAlwaysInSlot || info_->isLazilyCachedInSlot);
    return info_->slotIndex;
  }
  bool valueMayBeInSlot() const { return info_->isLazilyCachedInSlot; }

  bool baseCongruentTo(const MGetDOMPropertyBase* ins) const {
    if (!isDomMovable()) {
      return false;
    }

    // Checking the jitinfo is the same as checking the constant function
    if (!(info() == ins->info())) {
      return false;
    }

    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    JSJitInfo::AliasSet aliasSet = domAliasSet();
    if (aliasSet == JSJitInfo::AliasNone) {
      return AliasSet::None();
    }
    if (aliasSet == JSJitInfo::AliasDOMSets) {
      return AliasSet::Load(AliasSet::DOMProperty);
    }
    MOZ_ASSERT(aliasSet == JSJitInfo::AliasEverything);
    return AliasSet::Store(AliasSet::Any);
  }
};

class MGetDOMProperty : public MGetDOMPropertyBase {
  Realm* getterRealm_;
  DOMObjectKind objectKind_;

  MGetDOMProperty(const JSJitInfo* jitinfo, DOMObjectKind objectKind,
                  Realm* getterRealm)
      : MGetDOMPropertyBase(classOpcode, jitinfo),
        getterRealm_(getterRealm),
        objectKind_(objectKind) {}

 public:
  INSTRUCTION_HEADER(GetDOMProperty)

  static MGetDOMProperty* New(TempAllocator& alloc, const JSJitInfo* info,
                              DOMObjectKind objectKind, Realm* getterRealm,
                              MDefinition* obj, MDefinition* guard,
                              MDefinition* globalGuard) {
    auto* res = new (alloc) MGetDOMProperty(info, objectKind, getterRealm);
    if (!res || !res->init(alloc, obj, guard, globalGuard)) {
      return nullptr;
    }
    return res;
  }

  Realm* getterRealm() const { return getterRealm_; }
  DOMObjectKind objectKind() const { return objectKind_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGetDOMProperty()) {
      return false;
    }

    if (ins->toGetDOMProperty()->getterRealm() != getterRealm()) {
      return false;
    }

    return baseCongruentTo(ins->toGetDOMProperty());
  }

  bool possiblyCalls() const override { return true; }
};

class MGetDOMMember : public MGetDOMPropertyBase {
  explicit MGetDOMMember(const JSJitInfo* jitinfo)
      : MGetDOMPropertyBase(classOpcode, jitinfo) {
    setResultType(MIRTypeFromValueType(jitinfo->returnType()));
  }

 public:
  INSTRUCTION_HEADER(GetDOMMember)

  static MGetDOMMember* New(TempAllocator& alloc, const JSJitInfo* info,
                            MDefinition* obj, MDefinition* guard,
                            MDefinition* globalGuard) {
    auto* res = new (alloc) MGetDOMMember(info);
    if (!res || !res->init(alloc, obj, guard, globalGuard)) {
      return nullptr;
    }
    return res;
  }

  bool possiblyCalls() const override { return false; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGetDOMMember()) {
      return false;
    }

    return baseCongruentTo(ins->toGetDOMMember());
  }
};

class MLoadDOMExpandoValueGuardGeneration : public MUnaryInstruction,
                                            public SingleObjectPolicy::Data {
  JS::ExpandoAndGeneration* expandoAndGeneration_;
  uint64_t generation_;

  MLoadDOMExpandoValueGuardGeneration(
      MDefinition* proxy, JS::ExpandoAndGeneration* expandoAndGeneration,
      uint64_t generation)
      : MUnaryInstruction(classOpcode, proxy),
        expandoAndGeneration_(expandoAndGeneration),
        generation_(generation) {
    setGuard();
    setMovable();
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(LoadDOMExpandoValueGuardGeneration)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, proxy))

  JS::ExpandoAndGeneration* expandoAndGeneration() const {
    return expandoAndGeneration_;
  }
  uint64_t generation() const { return generation_; }

  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isLoadDOMExpandoValueGuardGeneration()) {
      return false;
    }
    const auto* other = ins->toLoadDOMExpandoValueGuardGeneration();
    if (expandoAndGeneration() != other->expandoAndGeneration() ||
        generation() != other->generation()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::DOMProxyExpando);
  }
};

// Inlined assembly for Math.floor(double | float32) -> int32.
class MFloor : public MUnaryInstruction, public FloatingPointPolicy<0>::Data {
  explicit MFloor(MDefinition* num) : MUnaryInstruction(classOpcode, num) {
    setResultType(MIRType::Int32);
    specialization_ = MIRType::Double;
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Floor)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  void computeRange(TempAllocator& alloc) override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MFloor)
};

// Inlined assembly version for Math.ceil(double | float32) -> int32.
class MCeil : public MUnaryInstruction, public FloatingPointPolicy<0>::Data {
  explicit MCeil(MDefinition* num) : MUnaryInstruction(classOpcode, num) {
    setResultType(MIRType::Int32);
    specialization_ = MIRType::Double;
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Ceil)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  void computeRange(TempAllocator& alloc) override;
  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MCeil)
};

// Inlined version of Math.round(double | float32) -> int32.
class MRound : public MUnaryInstruction, public FloatingPointPolicy<0>::Data {
  explicit MRound(MDefinition* num) : MUnaryInstruction(classOpcode, num) {
    setResultType(MIRType::Int32);
    specialization_ = MIRType::Double;
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Round)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MRound)
};

// Inlined version of Math.trunc(double | float32) -> int32.
class MTrunc : public MUnaryInstruction, public FloatingPointPolicy<0>::Data {
  explicit MTrunc(MDefinition* num) : MUnaryInstruction(classOpcode, num) {
    setResultType(MIRType::Int32);
    specialization_ = MIRType::Double;
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(Trunc)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MTrunc)
};

// NearbyInt rounds the floating-point input to the nearest integer, according
// to the RoundingMode.
class MNearbyInt : public MUnaryInstruction,
                   public FloatingPointPolicy<0>::Data {
  RoundingMode roundingMode_;

  explicit MNearbyInt(MDefinition* num, MIRType resultType,
                      RoundingMode roundingMode)
      : MUnaryInstruction(classOpcode, num), roundingMode_(roundingMode) {
    MOZ_ASSERT(HasAssemblerSupport(roundingMode));

    MOZ_ASSERT(IsFloatingPointType(resultType));
    setResultType(resultType);
    specialization_ = resultType;

    setMovable();
  }

 public:
  INSTRUCTION_HEADER(NearbyInt)
  TRIVIAL_NEW_WRAPPERS

  static bool HasAssemblerSupport(RoundingMode mode) {
    return Assembler::HasRoundInstruction(mode);
  }

  RoundingMode roundingMode() const { return roundingMode_; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool isFloat32Commutative() const override { return true; }
  void trySpecializeFloat32(TempAllocator& alloc) override;
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#endif

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toNearbyInt()->roundingMode() == roundingMode_;
  }

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

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;

  bool canRecoverOnBailout() const override {
    switch (roundingMode_) {
      case RoundingMode::Up:
      case RoundingMode::Down:
      case RoundingMode::TowardsZero:
        return true;
      default:
        return false;
    }
  }

  ALLOW_CLONE(MNearbyInt)
};

class MGetIteratorCache : public MUnaryInstruction,
                          public BoxExceptPolicy<0, MIRType::Object>::Data {
  explicit MGetIteratorCache(MDefinition* val)
      : MUnaryInstruction(classOpcode, val) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(GetIteratorCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value))
};

// Implementation for 'in' operator using instruction cache
class MInCache : public MBinaryInstruction,
                 public MixPolicy<CacheIdPolicy<0>, ObjectPolicy<1>>::Data {
  MInCache(MDefinition* key, MDefinition* obj)
      : MBinaryInstruction(classOpcode, key, obj) {
    setResultType(MIRType::Boolean);
  }

 public:
  INSTRUCTION_HEADER(InCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, key), (1, object))
};

// Test whether the index is in the array bounds or a hole.
class MInArray : public MQuaternaryInstruction, public ObjectPolicy<3>::Data {
  bool needsNegativeIntCheck_;

  MInArray(MDefinition* elements, MDefinition* index, MDefinition* initLength,
           MDefinition* object)
      : MQuaternaryInstruction(classOpcode, elements, index, initLength,
                               object),
        needsNegativeIntCheck_(true) {
    setResultType(MIRType::Boolean);
    setMovable();

    // Set the guard flag to make sure we bail when we see a negative index.
    // We can clear this flag (and needsNegativeIntCheck_) in
    // collectRangeInfoPreTrunc.
    setGuard();

    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
    MOZ_ASSERT(initLength->type() == MIRType::Int32);
  }

 public:
  INSTRUCTION_HEADER(InArray)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, initLength), (3, object))

  bool needsNegativeIntCheck() const { return needsNegativeIntCheck_; }
  void collectRangeInfoPreTrunc() override;
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::Element);
  }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isInArray()) {
      return false;
    }
    const MInArray* other = ins->toInArray();
    if (needsNegativeIntCheck() != other->needsNegativeIntCheck()) {
      return false;
    }
    return congruentIfOperandsEqual(other);
  }
};

// Bail when the element is a hole.
class MGuardElementNotHole : public MBinaryInstruction,
                             public NoTypePolicy::Data {
  MGuardElementNotHole(MDefinition* elements, MDefinition* index)
      : MBinaryInstruction(classOpcode, elements, index) {
    setMovable();
    setGuard();
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::Int32);
  }

 public:
  INSTRUCTION_HEADER(GuardElementNotHole)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index))

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::Element);
  }
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
};

class MCheckPrivateFieldCache
    : public MBinaryInstruction,
      public MixPolicy<BoxExceptPolicy<0, MIRType::Object>,
                       CacheIdPolicy<1>>::Data {
  MCheckPrivateFieldCache(MDefinition* obj, MDefinition* id)
      : MBinaryInstruction(classOpcode, obj, id) {
    setResultType(MIRType::Boolean);
  }

 public:
  INSTRUCTION_HEADER(CheckPrivateFieldCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value), (1, idval))
};

class MHasOwnCache : public MBinaryInstruction,
                     public MixPolicy<BoxExceptPolicy<0, MIRType::Object>,
                                      CacheIdPolicy<1>>::Data {
  MHasOwnCache(MDefinition* obj, MDefinition* id)
      : MBinaryInstruction(classOpcode, obj, id) {
    setResultType(MIRType::Boolean);
  }

 public:
  INSTRUCTION_HEADER(HasOwnCache)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value), (1, idval))
};

// Implementation for instanceof operator with specific rhs.
class MInstanceOf : public MBinaryInstruction,
                    public MixPolicy<BoxExceptPolicy<0, MIRType::Object>,
                                     ObjectPolicy<1>>::Data {
  MInstanceOf(MDefinition* obj, MDefinition* proto)
      : MBinaryInstruction(classOpcode, obj, proto) {
    setResultType(MIRType::Boolean);
  }

 public:
  INSTRUCTION_HEADER(InstanceOf)
  TRIVIAL_NEW_WRAPPERS
};

// Given a value being written to another object, update the generational store
// buffer if the value is in the nursery and object is in the tenured heap.
class MPostWriteBarrier : public MBinaryInstruction,
                          public ObjectPolicy<0>::Data {
  MPostWriteBarrier(MDefinition* obj, MDefinition* value)
      : MBinaryInstruction(classOpcode, obj, value) {
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(PostWriteBarrier)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, value))

  AliasSet getAliasSet() const override { return AliasSet::None(); }

#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override {
    // During lowering, values that neither have object nor value MIR type
    // are ignored, thus Float32 can show up at this point without any issue.
    return use == getUseFor(1);
  }
#endif

  ALLOW_CLONE(MPostWriteBarrier)
};

// Given a value being written to another object's elements at the specified
// index, update the generational store buffer if the value is in the nursery
// and object is in the tenured heap.
class MPostWriteElementBarrier
    : public MTernaryInstruction,
      public MixPolicy<ObjectPolicy<0>, UnboxedInt32Policy<2>>::Data {
  MPostWriteElementBarrier(MDefinition* obj, MDefinition* value,
                           MDefinition* index)
      : MTernaryInstruction(classOpcode, obj, value, index) {
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(PostWriteElementBarrier)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, value), (2, index))

  AliasSet getAliasSet() const override { return AliasSet::None(); }

#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override {
    // During lowering, values that neither have object nor value MIR type
    // are ignored, thus Float32 can show up at this point without any issue.
    return use == getUseFor(1);
  }
#endif

  ALLOW_CLONE(MPostWriteElementBarrier)
};

class MNewCallObject : public MUnaryInstruction,
                       public SingleObjectPolicy::Data {
 public:
  INSTRUCTION_HEADER(NewCallObject)
  TRIVIAL_NEW_WRAPPERS

  explicit MNewCallObject(MConstant* templateObj)
      : MUnaryInstruction(classOpcode, templateObj) {
    setResultType(MIRType::Object);
  }

  CallObject* templateObject() const {
    return &getOperand(0)->toConstant()->toObject().as<CallObject>();
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }
};

class MNewStringObject : public MUnaryInstruction,
                         public ConvertToStringPolicy<0>::Data {
  CompilerObject templateObj_;

  MNewStringObject(MDefinition* input, JSObject* templateObj)
      : MUnaryInstruction(classOpcode, input), templateObj_(templateObj) {
    setResultType(MIRType::Object);
  }

 public:
  INSTRUCTION_HEADER(NewStringObject)
  TRIVIAL_NEW_WRAPPERS

  StringObject* templateObj() const;
};

// This is an alias for MLoadFixedSlot.
class MEnclosingEnvironment : public MLoadFixedSlot {
  explicit MEnclosingEnvironment(MDefinition* obj)
      : MLoadFixedSlot(obj, EnvironmentObject::enclosingEnvironmentSlot()) {
    setResultType(MIRType::Object);
  }

 public:
  static MEnclosingEnvironment* New(TempAllocator& alloc, MDefinition* obj) {
    return new (alloc) MEnclosingEnvironment(obj);
  }

  AliasSet getAliasSet() const override {
    // EnvironmentObject reserved slots are immutable.
    return AliasSet::None();
  }
};

// This is an element of a spaghetti stack which is used to represent the memory
// context which has to be restored in case of a bailout.
struct MStoreToRecover : public TempObject,
                         public InlineSpaghettiStackNode<MStoreToRecover> {
  MDefinition* operand;

  explicit MStoreToRecover(MDefinition* operand) : operand(operand) {}
};

using MStoresToRecoverList = InlineSpaghettiStack<MStoreToRecover>;

// A resume point contains the information needed to reconstruct the Baseline
// Interpreter state from a position in Warp JIT code. A resume point is a
// mapping of stack slots to MDefinitions.
//
// We capture stack state at critical points:
//   * (1) At the beginning of every basic block.
//   * (2) After every effectful operation.
//
// As long as these two properties are maintained, instructions can be moved,
// hoisted, or, eliminated without problems, and ops without side effects do not
// need to worry about capturing state at precisely the right point in time.
//
// Effectful instructions, of course, need to capture state after completion,
// where the interpreter will not attempt to repeat the operation. For this,
// ResumeAfter must be used. The state is attached directly to the effectful
// instruction to ensure that no intermediate instructions could be injected
// in between by a future analysis pass.
//
// During LIR construction, if an instruction can bail back to the interpreter,
// we create an LSnapshot, which uses the last known resume point to request
// register/stack assignments for every live value.
class MResumePoint final : public MNode
#ifdef DEBUG
    ,
                           public InlineForwardListNode<MResumePoint>
#endif
{
 private:
  friend class MBasicBlock;
  friend void AssertBasicGraphCoherency(MIRGraph& graph, bool force);

  // List of stack slots needed to reconstruct the BaselineFrame.
  FixedList<MUse> operands_;

  // List of stores needed to reconstruct the content of objects which are
  // emulated by EmulateStateOf variants.
  MStoresToRecoverList stores_;

  jsbytecode* pc_;
  MInstruction* instruction_;
  ResumeMode mode_;
  bool isDiscarded_ = false;

  MResumePoint(MBasicBlock* block, jsbytecode* pc, ResumeMode mode);
  void inherit(MBasicBlock* state);

  // Calling isDefinition or isResumePoint on MResumePoint is unnecessary.
  bool isDefinition() const = delete;
  bool isResumePoint() const = delete;

  void setBlock(MBasicBlock* block) {
    setBlockAndKind(block, Kind::ResumePoint);
  }

 protected:
  // Initializes operands_ to an empty array of a fixed length.
  // The array may then be filled in by inherit().
  [[nodiscard]] bool init(TempAllocator& alloc);

  void clearOperand(size_t index) {
    // FixedList doesn't initialize its elements, so do an unchecked init.
    operands_[index].initUncheckedWithoutProducer(this);
  }

  MUse* getUseFor(size_t index) override { return &operands_[index]; }
  const MUse* getUseFor(size_t index) const override {
    return &operands_[index];
  }

 public:
  static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block,
                           jsbytecode* pc, ResumeMode mode);

  MBasicBlock* block() const { return resumePointBlock(); }

  size_t numAllocatedOperands() const { return operands_.length(); }
  uint32_t stackDepth() const { return numAllocatedOperands(); }
  size_t numOperands() const override { return numAllocatedOperands(); }
  size_t indexOf(const MUse* u) const final {
    MOZ_ASSERT(u >= &operands_[0]);
    MOZ_ASSERT(u <= &operands_[numOperands() - 1]);
    return u - &operands_[0];
  }
  void initOperand(size_t index, MDefinition* operand) {
    // FixedList doesn't initialize its elements, so do an unchecked init.
    operands_[index].initUnchecked(operand, this);
  }
  void replaceOperand(size_t index, MDefinition* operand) final {
    operands_[index].replaceProducer(operand);
  }

  bool isObservableOperand(MUse* u) const;
  bool isObservableOperand(size_t index) const;
  bool isRecoverableOperand(MUse* u) const;

  MDefinition* getOperand(size_t index) const override {
    return operands_[index].producer();
  }
  jsbytecode* pc() const { return pc_; }
  MResumePoint* caller() const;
  uint32_t frameCount() const {
    uint32_t count = 1;
    for (MResumePoint* it = caller(); it; it = it->caller()) {
      count++;
    }
    return count;
  }
  MInstruction* instruction() { return instruction_; }
  void setInstruction(MInstruction* ins) {
    MOZ_ASSERT(!instruction_);
    instruction_ = ins;
  }
  void resetInstruction() {
    MOZ_ASSERT(instruction_);
    instruction_ = nullptr;
  }
  ResumeMode mode() const { return mode_; }

  void releaseUses() {
    for (size_t i = 0, e = numOperands(); i < e; i++) {
      if (operands_[i].hasProducer()) {
        operands_[i].releaseProducer();
      }
    }
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;

  // Register a store instruction on the current resume point. This
  // instruction would be recovered when we are bailing out. The |cache|
  // argument can be any resume point, it is used to share memory if we are
  // doing the same modification.
  void addStore(TempAllocator& alloc, MDefinition* store,
                const MResumePoint* cache = nullptr);

  MStoresToRecoverList::iterator storesBegin() const { return stores_.begin(); }
  MStoresToRecoverList::iterator storesEnd() const { return stores_.end(); }

  void setDiscarded() { isDiscarded_ = true; }
  bool isDiscarded() const { return isDiscarded_; }

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

class MIsCallable : public MUnaryInstruction,
                    public BoxExceptPolicy<0, MIRType::Object>::Data {
  explicit MIsCallable(MDefinition* object)
      : MUnaryInstruction(classOpcode, object) {
    setResultType(MIRType::Boolean);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(IsCallable)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MHasClass : public MUnaryInstruction, public SingleObjectPolicy::Data {
  const JSClass* class_;

  MHasClass(MDefinition* object, const JSClass* clasp)
      : MUnaryInstruction(classOpcode, object), class_(clasp) {
    MOZ_ASSERT(object->type() == MIRType::Object);
    setResultType(MIRType::Boolean);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(HasClass)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  const JSClass* getClass() const { return class_; }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isHasClass()) {
      return false;
    }
    if (getClass() != ins->toHasClass()->getClass()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
};

class MGuardToClass : public MUnaryInstruction,
                      public SingleObjectPolicy::Data {
  const JSClass* class_;

  MGuardToClass(MDefinition* object, const JSClass* clasp)
      : MUnaryInstruction(classOpcode, object), class_(clasp) {
    MOZ_ASSERT(object->type() == MIRType::Object);
    MOZ_ASSERT(!clasp->isJSFunction(), "Use MGuardToFunction instead");
    setResultType(MIRType::Object);
    setMovable();

    // We will bail out if the class type is incorrect, so we need to ensure we
    // don't eliminate this instruction
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(GuardToClass)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  const JSClass* getClass() const { return class_; }
  bool isArgumentsObjectClass() const {
    return class_ == &MappedArgumentsObject::class_ ||
           class_ == &UnmappedArgumentsObject::class_;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardToClass()) {
      return false;
    }
    if (getClass() != ins->toGuardToClass()->getClass()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
};

class MGuardToFunction : public MUnaryInstruction,
                         public SingleObjectPolicy::Data {
  explicit MGuardToFunction(MDefinition* object)
      : MUnaryInstruction(classOpcode, object) {
    MOZ_ASSERT(object->type() == MIRType::Object);
    setResultType(MIRType::Object);
    setMovable();

    // We will bail out if the class type is incorrect, so we need to ensure we
    // don't eliminate this instruction
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(GuardToFunction)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  MDefinition* foldsTo(TempAllocator& alloc) override;
  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    if (!ins->isGuardToFunction()) {
      return false;
    }
    return congruentIfOperandsEqual(ins);
  }
};

// Note: we might call a proxy trap, so this instruction is effectful.
class MIsArray : public MUnaryInstruction,
                 public BoxExceptPolicy<0, MIRType::Object>::Data {
  explicit MIsArray(MDefinition* value)
      : MUnaryInstruction(classOpcode, value) {
    setResultType(MIRType::Boolean);
  }

 public:
  INSTRUCTION_HEADER(IsArray)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value))

  MDefinition* foldsTo(TempAllocator& alloc) override;
};

class MIsTypedArray : public MUnaryInstruction,
                      public SingleObjectPolicy::Data {
  bool possiblyWrapped_;

  explicit MIsTypedArray(MDefinition* value, bool possiblyWrapped)
      : MUnaryInstruction(classOpcode, value),
        possiblyWrapped_(possiblyWrapped) {
    setResultType(MIRType::Boolean);

    if (possiblyWrapped) {
      // Proxy checks may throw, so we're neither removable nor movable.
      setGuard();
    } else {
      setMovable();
    }
  }

 public:
  INSTRUCTION_HEADER(IsTypedArray)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value))

  bool isPossiblyWrapped() const { return possiblyWrapped_; }
  AliasSet getAliasSet() const override {
    if (isPossiblyWrapped()) {
      return AliasSet::Store(AliasSet::Any);
    }
    return AliasSet::None();
  }
};

// Allocate the generator object for a frame.
class MGenerator : public MTernaryInstruction,
                   public MixPolicy<ObjectPolicy<0>, ObjectPolicy<1>>::Data {
  explicit MGenerator(MDefinition* callee, MDefinition* environmentChain,
                      MDefinition* argsObject)
      : MTernaryInstruction(classOpcode, callee, environmentChain, argsObject) {
    setResultType(MIRType::Object);
  };

 public:
  INSTRUCTION_HEADER(Generator)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, callee), (1, environmentChain), (2, argsObject))
};

class MMaybeExtractAwaitValue : public MBinaryInstruction,
                                public BoxPolicy<0>::Data {
  explicit MMaybeExtractAwaitValue(MDefinition* value, MDefinition* canSkip)
      : MBinaryInstruction(classOpcode, value, canSkip) {
    setResultType(MIRType::Value);
  }

 public:
  INSTRUCTION_HEADER(MaybeExtractAwaitValue)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value), (1, canSkip))
};

class MAtomicIsLockFree : public MUnaryInstruction,
                          public ConvertToInt32Policy<0>::Data {
  explicit MAtomicIsLockFree(MDefinition* value)
      : MUnaryInstruction(classOpcode, value) {
    setResultType(MIRType::Boolean);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(AtomicIsLockFree)
  TRIVIAL_NEW_WRAPPERS

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  [[nodiscard]] bool writeRecoverData(
      CompactBufferWriter& writer) const override;
  bool canRecoverOnBailout() const override { return true; }

  ALLOW_CLONE(MAtomicIsLockFree)
};

class MCompareExchangeTypedArrayElement
    : public MQuaternaryInstruction,
      public MixPolicy<TruncateToInt32OrToBigIntPolicy<2>,
                       TruncateToInt32OrToBigIntPolicy<3>>::Data {
  Scalar::Type arrayType_;

  explicit MCompareExchangeTypedArrayElement(MDefinition* elements,
                                             MDefinition* index,
                                             Scalar::Type arrayType,
                                             MDefinition* oldval,
                                             MDefinition* newval)
      : MQuaternaryInstruction(classOpcode, elements, index, oldval, newval),
        arrayType_(arrayType) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    setGuard();  // Not removable
  }

 public:
  INSTRUCTION_HEADER(CompareExchangeTypedArrayElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, oldval), (3, newval))

  bool isByteArray() const {
    return (arrayType_ == Scalar::Int8 || arrayType_ == Scalar::Uint8);
  }
  Scalar::Type arrayType() const { return arrayType_; }
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::UnboxedElement);
  }
};

class MAtomicExchangeTypedArrayElement
    : public MTernaryInstruction,
      public TruncateToInt32OrToBigIntPolicy<2>::Data {
  Scalar::Type arrayType_;

  MAtomicExchangeTypedArrayElement(MDefinition* elements, MDefinition* index,
                                   MDefinition* value, Scalar::Type arrayType)
      : MTernaryInstruction(classOpcode, elements, index, value),
        arrayType_(arrayType) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(arrayType <= Scalar::Uint32 || Scalar::isBigIntType(arrayType));
    setGuard();  // Not removable
  }

 public:
  INSTRUCTION_HEADER(AtomicExchangeTypedArrayElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, value))

  bool isByteArray() const {
    return (arrayType_ == Scalar::Int8 || arrayType_ == Scalar::Uint8);
  }
  Scalar::Type arrayType() const { return arrayType_; }
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::UnboxedElement);
  }
};

class MAtomicTypedArrayElementBinop
    : public MTernaryInstruction,
      public TruncateToInt32OrToBigIntPolicy<2>::Data {
 private:
  AtomicOp op_;
  Scalar::Type arrayType_;
  bool forEffect_;

  explicit MAtomicTypedArrayElementBinop(AtomicOp op, MDefinition* elements,
                                         MDefinition* index,
                                         Scalar::Type arrayType,
                                         MDefinition* value, bool forEffect)
      : MTernaryInstruction(classOpcode, elements, index, value),
        op_(op),
        arrayType_(arrayType),
        forEffect_(forEffect) {
    MOZ_ASSERT(elements->type() == MIRType::Elements);
    MOZ_ASSERT(index->type() == MIRType::IntPtr);
    MOZ_ASSERT(arrayType <= Scalar::Uint32 || Scalar::isBigIntType(arrayType));
    setGuard();  // Not removable
  }

 public:
  INSTRUCTION_HEADER(AtomicTypedArrayElementBinop)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements), (1, index), (2, value))

  bool isByteArray() const {
    return (arrayType_ == Scalar::Int8 || arrayType_ == Scalar::Uint8);
  }
  AtomicOp operation() const { return op_; }
  Scalar::Type arrayType() const { return arrayType_; }
  bool isForEffect() const { return forEffect_; }
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::UnboxedElement);
  }
};

class MDebugger : public MNullaryInstruction {
  MDebugger() : MNullaryInstruction(classOpcode) {
    setBailoutKind(BailoutKind::Debugger);
  }

 public:
  INSTRUCTION_HEADER(Debugger)
  TRIVIAL_NEW_WRAPPERS
};

// Used to load the prototype of an object known to have
// a static prototype.
class MObjectStaticProto : public MUnaryInstruction,
                           public SingleObjectPolicy::Data {
  explicit MObjectStaticProto(MDefinition* object)
      : MUnaryInstruction(classOpcode, object) {
    setResultType(MIRType::Object);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(ObjectStaticProto)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::ObjectFields);
  }
  AliasType mightAlias(const MDefinition* def) const override {
    // These instructions never modify the [[Prototype]].
    if (def->isAddAndStoreSlot() || def->isAllocateAndStoreSlot() ||
        def->isStoreElementHole() || def->isArrayPush()) {
      return AliasType::NoAlias;
    }
    return AliasType::MayAlias;
  }
};

class MConstantProto : public MUnaryInstruction,
                       public SingleObjectPolicy::Data {
  // NOTE: we're not going to actually use the underlying receiver object for
  // anything. This is just here for giving extra information to MGuardShape
  // to MGuardShape::mightAlias. Accordingly, we don't take it as an operand,
  // but instead just keep a pointer to it. This means we need to ensure it's
  // not discarded before we try to access it. If this is discarded, we
  // basically just become an MConstant for the object's proto, which is fine.
  const MDefinition* receiverObject_;

  explicit MConstantProto(MDefinition* protoObject,
                          const MDefinition* receiverObject)
      : MUnaryInstruction(classOpcode, protoObject),
        receiverObject_(receiverObject) {
    MOZ_ASSERT(protoObject->isConstant());
    setResultType(MIRType::Object);
    setMovable();
  }

  ALLOW_CLONE(MConstantProto)

 public:
  INSTRUCTION_HEADER(ConstantProto)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, protoObject))

  HashNumber valueHash() const override;

  bool congruentTo(const MDefinition* ins) const override {
    if (this == ins) {
      return true;
    }
    const MDefinition* receiverObject = getReceiverObject();
    return congruentIfOperandsEqual(ins) && receiverObject &&
           receiverObject == ins->toConstantProto()->getReceiverObject();
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  const MDefinition* getReceiverObject() const {
    if (receiverObject_->isDiscarded()) {
      return nullptr;
    }
    return receiverObject_;
  }
};

class MObjectToIterator : public MUnaryInstruction,
                          public ObjectPolicy<0>::Data {
  NativeIteratorListHead* enumeratorsAddr_;
  bool wantsIndices_ = false;

  explicit MObjectToIterator(MDefinition* object,
                             NativeIteratorListHead* enumeratorsAddr)
      : MUnaryInstruction(classOpcode, object),
        enumeratorsAddr_(enumeratorsAddr) {
    setResultType(MIRType::Object);
  }

 public:
  NativeIteratorListHead* enumeratorsAddr() const { return enumeratorsAddr_; }
  INSTRUCTION_HEADER(ObjectToIterator)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  bool wantsIndices() const { return wantsIndices_; }
  void setWantsIndices(bool value) { wantsIndices_ = value; }
};

// Flips the input's sign bit, independently of the rest of the number's
// payload. Note this is different from multiplying by minus-one, which has
// side-effects for e.g. NaNs.
class MWasmNeg : public MUnaryInstruction, public NoTypePolicy::Data {
  MWasmNeg(MDefinition* op, MIRType type) : MUnaryInstruction(classOpcode, op) {
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmNeg)
  TRIVIAL_NEW_WRAPPERS
};

// Machine-level bitwise AND/OR/XOR, avoiding all JS-level complexity embodied
// in MBinaryBitwiseInstruction.
class MWasmBinaryBitwise : public MBinaryInstruction,
                           public NoTypePolicy::Data {
 public:
  enum class SubOpcode { And, Or, Xor };

 protected:
  MWasmBinaryBitwise(MDefinition* left, MDefinition* right, MIRType type,
                     SubOpcode subOpcode)
      : MBinaryInstruction(classOpcode, left, right), subOpcode_(subOpcode) {
    MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64);
    setResultType(type);
    setMovable();
    setCommutative();
  }

 public:
  INSTRUCTION_HEADER(WasmBinaryBitwise)
  TRIVIAL_NEW_WRAPPERS

  SubOpcode subOpcode() const { return subOpcode_; }
  MDefinition* foldsTo(TempAllocator& alloc) override;

  bool congruentTo(const MDefinition* ins) const override {
    return ins->isWasmBinaryBitwise() &&
           ins->toWasmBinaryBitwise()->subOpcode() == subOpcode() &&
           binaryCongruentTo(ins);
  }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    const char* what = "!!unknown!!";
    switch (subOpcode()) {
      case SubOpcode::And:
        what = "And";
        break;
      case SubOpcode::Or:
        what = "Or";
        break;
      case SubOpcode::Xor:
        what = "Xor";
        break;
    }
    extras->add(what);
  }
#endif

 private:
  SubOpcode subOpcode_;

  ALLOW_CLONE(MWasmBinaryBitwise)
};

class MWasmLoadInstance : public MUnaryInstruction, public NoTypePolicy::Data {
  uint32_t offset_;
  AliasSet aliases_;

  explicit MWasmLoadInstance(MDefinition* instance, uint32_t offset,
                             MIRType type, AliasSet aliases)
      : MUnaryInstruction(classOpcode, instance),
        offset_(offset),
        aliases_(aliases) {
    // Different instance data have different alias classes and only those
    // classes are allowed.
    MOZ_ASSERT(
        aliases_.flags() == AliasSet::Load(AliasSet::WasmHeapMeta).flags() ||
        aliases_.flags() == AliasSet::Load(AliasSet::WasmTableMeta).flags() ||
        aliases_.flags() ==
            AliasSet::Load(AliasSet::WasmPendingException).flags() ||
        aliases_.flags() == AliasSet::None().flags());

    // The only types supported at the moment.
    MOZ_ASSERT(type == MIRType::Pointer || type == MIRType::Int32 ||
               type == MIRType::Int64 || type == MIRType::RefOrNull);

    setMovable();
    setResultType(type);
  }

 public:
  INSTRUCTION_HEADER(WasmLoadInstance)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance))

  uint32_t offset() const { return offset_; }

  bool congruentTo(const MDefinition* ins) const override {
    return op() == ins->op() &&
           offset() == ins->toWasmLoadInstance()->offset() &&
           type() == ins->type();
  }

  HashNumber valueHash() const override {
    return addU32ToHash(HashNumber(op()), offset());
  }

  AliasSet getAliasSet() const override { return aliases_; }
};

class MWasmStoreInstance : public MBinaryInstruction,
                           public NoTypePolicy::Data {
  uint32_t offset_;
  AliasSet aliases_;

  explicit MWasmStoreInstance(MDefinition* instance, MDefinition* value,
                              uint32_t offset, MIRType type, AliasSet aliases)
      : MBinaryInstruction(classOpcode, instance, value),
        offset_(offset),
        aliases_(aliases) {
    // Different instance data have different alias classes and only those
    // classes are allowed.
    MOZ_ASSERT(aliases_.flags() ==
               AliasSet::Store(AliasSet::WasmPendingException).flags());

    // The only types supported at the moment.
    MOZ_ASSERT(type == MIRType::Pointer || type == MIRType::Int32 ||
               type == MIRType::Int64 || type == MIRType::RefOrNull);
  }

 public:
  INSTRUCTION_HEADER(WasmStoreInstance)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance), (1, value))

  uint32_t offset() const { return offset_; }

  AliasSet getAliasSet() const override { return aliases_; }
};

class MWasmHeapReg : public MNullaryInstruction {
  AliasSet aliases_;

  explicit MWasmHeapReg(AliasSet aliases)
      : MNullaryInstruction(classOpcode), aliases_(aliases) {
    setMovable();
    setResultType(MIRType::Pointer);
  }

 public:
  INSTRUCTION_HEADER(WasmHeapReg)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return ins->isWasmHeapReg();
  }

  AliasSet getAliasSet() const override { return aliases_; }
};

// For memory32, bounds check nodes are of type Int32 on 32-bit systems for both
// wasm and asm.js code, as well as on 64-bit systems for asm.js code and for
// wasm code that is known to have a bounds check limit that fits into 32 bits.
// They are of type Int64 only on 64-bit systems for wasm code with 4GB heaps.
// There is no way for nodes of both types to be present in the same function.
// Should this change, then BCE must be updated to take type into account.
//
// For memory64, bounds check nodes are always of type Int64.

class MWasmBoundsCheck : public MBinaryInstruction, public NoTypePolicy::Data {
 public:
  enum Target {
    // Linear memory at index zero, which is the only memory allowed so far.
    Memory0,
    // Everything else.  Currently comprises tables, and arrays in the GC
    // proposal.
    Unknown
  };

 private:
  wasm::BytecodeOffset bytecodeOffset_;
  Target target_;

  explicit MWasmBoundsCheck(MDefinition* index, MDefinition* boundsCheckLimit,
                            wasm::BytecodeOffset bytecodeOffset, Target target)
      : MBinaryInstruction(classOpcode, index, boundsCheckLimit),
        bytecodeOffset_(bytecodeOffset),
        target_(target) {
    MOZ_ASSERT(index->type() == boundsCheckLimit->type());

    // Bounds check is effectful: it throws for OOB.
    setGuard();

    if (JitOptions.spectreIndexMasking) {
      setResultType(index->type());
    }
  }

 public:
  INSTRUCTION_HEADER(WasmBoundsCheck)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, index), (1, boundsCheckLimit))

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool isMemory0() const { return target_ == MWasmBoundsCheck::Memory0; }

  bool isRedundant() const { return !isGuard(); }

  void setRedundant() { setNotGuard(); }

  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};

class MWasmAddOffset : public MUnaryInstruction, public NoTypePolicy::Data {
  uint64_t offset_;
  wasm::BytecodeOffset bytecodeOffset_;

  MWasmAddOffset(MDefinition* base, uint64_t offset,
                 wasm::BytecodeOffset bytecodeOffset)
      : MUnaryInstruction(classOpcode, base),
        offset_(offset),
        bytecodeOffset_(bytecodeOffset) {
    setGuard();
    MOZ_ASSERT(base->type() == MIRType::Int32 ||
               base->type() == MIRType::Int64);
    setResultType(base->type());
  }

 public:
  INSTRUCTION_HEADER(WasmAddOffset)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, base))

  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  uint64_t offset() const { return offset_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};

class MWasmAlignmentCheck : public MUnaryInstruction,
                            public NoTypePolicy::Data {
  uint32_t byteSize_;
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MWasmAlignmentCheck(MDefinition* index, uint32_t byteSize,
                               wasm::BytecodeOffset bytecodeOffset)
      : MUnaryInstruction(classOpcode, index),
        byteSize_(byteSize),
        bytecodeOffset_(bytecodeOffset) {
    MOZ_ASSERT(mozilla::IsPowerOfTwo(byteSize));
    // Alignment check is effectful: it throws for unaligned.
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(WasmAlignmentCheck)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, index))

  bool congruentTo(const MDefinition* ins) const override;

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  uint32_t byteSize() const { return byteSize_; }

  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
};

class MWasmLoad
    : public MVariadicInstruction,  // memoryBase is nullptr on some platforms
      public NoTypePolicy::Data {
  wasm::MemoryAccessDesc access_;

  explicit MWasmLoad(const wasm::MemoryAccessDesc& access, MIRType resultType)
      : MVariadicInstruction(classOpcode), access_(access) {
    setGuard();
    setResultType(resultType);
  }

 public:
  INSTRUCTION_HEADER(WasmLoad)
  NAMED_OPERANDS((0, base), (1, memoryBase));

  static MWasmLoad* New(TempAllocator& alloc, MDefinition* memoryBase,
                        MDefinition* base, const wasm::MemoryAccessDesc& access,
                        MIRType resultType) {
    MWasmLoad* load = new (alloc) MWasmLoad(access, resultType);
    if (!load->init(alloc, 1 + !!memoryBase)) {
      return nullptr;
    }

    load->initOperand(0, base);
    if (memoryBase) {
      load->initOperand(1, memoryBase);
    }

    return load;
  }

  const wasm::MemoryAccessDesc& access() const { return access_; }

  AliasSet getAliasSet() const override {
    // When a barrier is needed, make the instruction effectful by giving
    // it a "store" effect.
    if (access_.isAtomic()) {
      return AliasSet::Store(AliasSet::WasmHeap);
    }
    return AliasSet::Load(AliasSet::WasmHeap);
  }

  bool hasMemoryBase() const { return numOperands() > 1; }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "(offs=%lld)", (long long int)access().offset64());
    extras->add(buf);
  }
#endif
};

class MWasmStore : public MVariadicInstruction, public NoTypePolicy::Data {
  wasm::MemoryAccessDesc access_;

  explicit MWasmStore(const wasm::MemoryAccessDesc& access)
      : MVariadicInstruction(classOpcode), access_(access) {
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(WasmStore)
  NAMED_OPERANDS((0, base), (1, value), (2, memoryBase))

  static MWasmStore* New(TempAllocator& alloc, MDefinition* memoryBase,
                         MDefinition* base,
                         const wasm::MemoryAccessDesc& access,
                         MDefinition* value) {
    MWasmStore* store = new (alloc) MWasmStore(access);
    if (!store->init(alloc, 2 + !!memoryBase)) {
      return nullptr;
    }

    store->initOperand(0, base);
    store->initOperand(1, value);
    if (memoryBase) {
      store->initOperand(2, memoryBase);
    }

    return store;
  }

  const wasm::MemoryAccessDesc& access() const { return access_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmHeap);
  }

  bool hasMemoryBase() const { return numOperands() > 2; }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "(offs=%lld)", (long long int)access().offset64());
    extras->add(buf);
  }
#endif
};

class MAsmJSMemoryAccess {
  Scalar::Type accessType_;
  bool needsBoundsCheck_;

 public:
  explicit MAsmJSMemoryAccess(Scalar::Type accessType)
      : accessType_(accessType), needsBoundsCheck_(true) {
    MOZ_ASSERT(accessType != Scalar::Uint8Clamped);
  }

  Scalar::Type accessType() const { return accessType_; }
  unsigned byteSize() const { return TypedArrayElemSize(accessType()); }
  bool needsBoundsCheck() const { return needsBoundsCheck_; }

  wasm::MemoryAccessDesc access() const {
    return wasm::MemoryAccessDesc(0, accessType_, Scalar::byteSize(accessType_),
                                  0, wasm::BytecodeOffset(), false);
  }

  void removeBoundsCheck() { needsBoundsCheck_ = false; }
};

class MAsmJSLoadHeap
    : public MVariadicInstruction,  // 1 plus optional memoryBase and
                                    // boundsCheckLimit
      public MAsmJSMemoryAccess,
      public NoTypePolicy::Data {
  uint32_t memoryBaseIndex_;

  explicit MAsmJSLoadHeap(uint32_t memoryBaseIndex, Scalar::Type accessType)
      : MVariadicInstruction(classOpcode),
        MAsmJSMemoryAccess(accessType),
        memoryBaseIndex_(memoryBaseIndex) {
    setResultType(ScalarTypeToMIRType(accessType));
  }

 public:
  INSTRUCTION_HEADER(AsmJSLoadHeap)
  NAMED_OPERANDS((0, base), (1, boundsCheckLimit))

  static MAsmJSLoadHeap* New(TempAllocator& alloc, MDefinition* memoryBase,
                             MDefinition* base, MDefinition* boundsCheckLimit,
                             Scalar::Type accessType) {
    uint32_t nextIndex = 2;
    uint32_t memoryBaseIndex = memoryBase ? nextIndex++ : UINT32_MAX;

    MAsmJSLoadHeap* load =
        new (alloc) MAsmJSLoadHeap(memoryBaseIndex, accessType);
    if (!load->init(alloc, nextIndex)) {
      return nullptr;
    }

    load->initOperand(0, base);
    load->initOperand(1, boundsCheckLimit);
    if (memoryBase) {
      load->initOperand(memoryBaseIndex, memoryBase);
    }

    return load;
  }

  bool hasMemoryBase() const { return memoryBaseIndex_ != UINT32_MAX; }
  MDefinition* memoryBase() const {
    MOZ_ASSERT(hasMemoryBase());
    return getOperand(memoryBaseIndex_);
  }

  bool congruentTo(const MDefinition* ins) const override;
  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::WasmHeap);
  }
  AliasType mightAlias(const MDefinition* def) const override;
};

class MAsmJSStoreHeap
    : public MVariadicInstruction,  // 2 plus optional memoryBase and
                                    // boundsCheckLimit
      public MAsmJSMemoryAccess,
      public NoTypePolicy::Data {
  uint32_t memoryBaseIndex_;

  explicit MAsmJSStoreHeap(uint32_t memoryBaseIndex, Scalar::Type accessType)
      : MVariadicInstruction(classOpcode),
        MAsmJSMemoryAccess(accessType),
        memoryBaseIndex_(memoryBaseIndex) {}

 public:
  INSTRUCTION_HEADER(AsmJSStoreHeap)
  NAMED_OPERANDS((0, base), (1, value), (2, boundsCheckLimit))

  static MAsmJSStoreHeap* New(TempAllocator& alloc, MDefinition* memoryBase,
                              MDefinition* base, MDefinition* boundsCheckLimit,
                              Scalar::Type accessType, MDefinition* v) {
    uint32_t nextIndex = 3;
    uint32_t memoryBaseIndex = memoryBase ? nextIndex++ : UINT32_MAX;

    MAsmJSStoreHeap* store =
        new (alloc) MAsmJSStoreHeap(memoryBaseIndex, accessType);
    if (!store->init(alloc, nextIndex)) {
      return nullptr;
    }

    store->initOperand(0, base);
    store->initOperand(1, v);
    store->initOperand(2, boundsCheckLimit);
    if (memoryBase) {
      store->initOperand(memoryBaseIndex, memoryBase);
    }

    return store;
  }

  bool hasMemoryBase() const { return memoryBaseIndex_ != UINT32_MAX; }
  MDefinition* memoryBase() const {
    MOZ_ASSERT(hasMemoryBase());
    return getOperand(memoryBaseIndex_);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmHeap);
  }
};

class MWasmCompareExchangeHeap : public MVariadicInstruction,
                                 public NoTypePolicy::Data {
  wasm::MemoryAccessDesc access_;
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MWasmCompareExchangeHeap(const wasm::MemoryAccessDesc& access,
                                    wasm::BytecodeOffset bytecodeOffset)
      : MVariadicInstruction(classOpcode),
        access_(access),
        bytecodeOffset_(bytecodeOffset) {
    setGuard();  // Not removable
    setResultType(ScalarTypeToMIRType(access.type()));
  }

 public:
  INSTRUCTION_HEADER(WasmCompareExchangeHeap)
  NAMED_OPERANDS((0, base), (1, oldValue), (2, newValue), (3, instance),
                 (4, memoryBase))

  static MWasmCompareExchangeHeap* New(TempAllocator& alloc,
                                       wasm::BytecodeOffset bytecodeOffset,
                                       MDefinition* memoryBase,
                                       MDefinition* base,
                                       const wasm::MemoryAccessDesc& access,
                                       MDefinition* oldv, MDefinition* newv,
                                       MDefinition* instance) {
    MWasmCompareExchangeHeap* cas =
        new (alloc) MWasmCompareExchangeHeap(access, bytecodeOffset);
    if (!cas->init(alloc, 4 + !!memoryBase)) {
      return nullptr;
    }
    cas->initOperand(0, base);
    cas->initOperand(1, oldv);
    cas->initOperand(2, newv);
    cas->initOperand(3, instance);
    if (memoryBase) {
      cas->initOperand(4, memoryBase);
    }
    return cas;
  }

  const wasm::MemoryAccessDesc& access() const { return access_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmHeap);
  }

  bool hasMemoryBase() const { return numOperands() > 4; }
};

class MWasmAtomicExchangeHeap : public MVariadicInstruction,
                                public NoTypePolicy::Data {
  wasm::MemoryAccessDesc access_;
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MWasmAtomicExchangeHeap(const wasm::MemoryAccessDesc& access,
                                   wasm::BytecodeOffset bytecodeOffset)
      : MVariadicInstruction(classOpcode),
        access_(access),
        bytecodeOffset_(bytecodeOffset) {
    setGuard();  // Not removable
    setResultType(ScalarTypeToMIRType(access.type()));
  }

 public:
  INSTRUCTION_HEADER(WasmAtomicExchangeHeap)
  NAMED_OPERANDS((0, base), (1, value), (2, instance), (3, memoryBase))

  static MWasmAtomicExchangeHeap* New(TempAllocator& alloc,
                                      wasm::BytecodeOffset bytecodeOffset,
                                      MDefinition* memoryBase,
                                      MDefinition* base,
                                      const wasm::MemoryAccessDesc& access,
                                      MDefinition* value,
                                      MDefinition* instance) {
    MWasmAtomicExchangeHeap* xchg =
        new (alloc) MWasmAtomicExchangeHeap(access, bytecodeOffset);
    if (!xchg->init(alloc, 3 + !!memoryBase)) {
      return nullptr;
    }

    xchg->initOperand(0, base);
    xchg->initOperand(1, value);
    xchg->initOperand(2, instance);
    if (memoryBase) {
      xchg->initOperand(3, memoryBase);
    }

    return xchg;
  }

  const wasm::MemoryAccessDesc& access() const { return access_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmHeap);
  }

  bool hasMemoryBase() const { return numOperands() > 3; }
};

class MWasmAtomicBinopHeap : public MVariadicInstruction,
                             public NoTypePolicy::Data {
  AtomicOp op_;
  wasm::MemoryAccessDesc access_;
  wasm::BytecodeOffset bytecodeOffset_;

  explicit MWasmAtomicBinopHeap(AtomicOp op,
                                const wasm::MemoryAccessDesc& access,
                                wasm::BytecodeOffset bytecodeOffset)
      : MVariadicInstruction(classOpcode),
        op_(op),
        access_(access),
        bytecodeOffset_(bytecodeOffset) {
    setGuard();  // Not removable
    setResultType(ScalarTypeToMIRType(access.type()));
  }

 public:
  INSTRUCTION_HEADER(WasmAtomicBinopHeap)
  NAMED_OPERANDS((0, base), (1, value), (2, instance), (3, memoryBase))

  static MWasmAtomicBinopHeap* New(TempAllocator& alloc,
                                   wasm::BytecodeOffset bytecodeOffset,
                                   AtomicOp op, MDefinition* memoryBase,
                                   MDefinition* base,
                                   const wasm::MemoryAccessDesc& access,
                                   MDefinition* v, MDefinition* instance) {
    MWasmAtomicBinopHeap* binop =
        new (alloc) MWasmAtomicBinopHeap(op, access, bytecodeOffset);
    if (!binop->init(alloc, 3 + !!memoryBase)) {
      return nullptr;
    }

    binop->initOperand(0, base);
    binop->initOperand(1, v);
    binop->initOperand(2, instance);
    if (memoryBase) {
      binop->initOperand(3, memoryBase);
    }

    return binop;
  }

  AtomicOp operation() const { return op_; }
  const wasm::MemoryAccessDesc& access() const { return access_; }
  wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmHeap);
  }

  bool hasMemoryBase() const { return numOperands() > 3; }
};

class MWasmLoadInstanceDataField : public MUnaryInstruction,
                                   public NoTypePolicy::Data {
  MWasmLoadInstanceDataField(MIRType type, unsigned instanceDataOffset,
                             bool isConstant, MDefinition* instance)
      : MUnaryInstruction(classOpcode, instance),
        instanceDataOffset_(instanceDataOffset),
        isConstant_(isConstant) {
    MOZ_ASSERT(IsNumberType(type) || type == MIRType::Simd128 ||
               type == MIRType::Pointer || type == MIRType::RefOrNull);
    setResultType(type);
    setMovable();
  }

  unsigned instanceDataOffset_;
  bool isConstant_;

 public:
  INSTRUCTION_HEADER(WasmLoadInstanceDataField)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance))

  unsigned instanceDataOffset() const { return instanceDataOffset_; }

  HashNumber valueHash() const override;
  bool congruentTo(const MDefinition* ins) const override;
  MDefinition* foldsTo(TempAllocator& alloc) override;

  AliasSet getAliasSet() const override {
    return isConstant_ ? AliasSet::None()
                       : AliasSet::Load(AliasSet::WasmInstanceData);
  }

  AliasType mightAlias(const MDefinition* def) const override;
};

class MWasmLoadGlobalCell : public MUnaryInstruction,
                            public NoTypePolicy::Data {
  MWasmLoadGlobalCell(MIRType type, MDefinition* cellPtr)
      : MUnaryInstruction(classOpcode, cellPtr) {
    setResultType(type);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmLoadGlobalCell)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, cellPtr))

  // The default valueHash is good enough, because there are no non-operand
  // fields.
  bool congruentTo(const MDefinition* ins) const override;

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::WasmGlobalCell);
  }

  AliasType mightAlias(const MDefinition* def) const override;
};

class MWasmLoadTableElement : public MBinaryInstruction,
                              public NoTypePolicy::Data {
  MWasmLoadTableElement(MDefinition* elements, MDefinition* index)
      : MBinaryInstruction(classOpcode, elements, index) {
    setResultType(MIRType::RefOrNull);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmLoadTableElement)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, elements))
  NAMED_OPERANDS((1, index))

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::WasmTableElement);
  }
};

class MWasmStoreInstanceDataField : public MBinaryInstruction,
                                    public NoTypePolicy::Data {
  MWasmStoreInstanceDataField(unsigned instanceDataOffset, MDefinition* value,
                              MDefinition* instance)
      : MBinaryInstruction(classOpcode, value, instance),
        instanceDataOffset_(instanceDataOffset) {}

  unsigned instanceDataOffset_;

 public:
  INSTRUCTION_HEADER(WasmStoreInstanceDataField)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value), (1, instance))

  unsigned instanceDataOffset() const { return instanceDataOffset_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmInstanceData);
  }
};

class MWasmStoreGlobalCell : public MBinaryInstruction,
                             public NoTypePolicy::Data {
  MWasmStoreGlobalCell(MDefinition* value, MDefinition* cellPtr)
      : MBinaryInstruction(classOpcode, value, cellPtr) {}

 public:
  INSTRUCTION_HEADER(WasmStoreGlobalCell)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, value), (1, cellPtr))

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmGlobalCell);
  }
};

class MWasmStoreStackResult : public MBinaryInstruction,
                              public NoTypePolicy::Data {
  MWasmStoreStackResult(MDefinition* stackResultArea, uint32_t offset,
                        MDefinition* value)
      : MBinaryInstruction(classOpcode, stackResultArea, value),
        offset_(offset) {}

  uint32_t offset_;

 public:
  INSTRUCTION_HEADER(WasmStoreStackResult)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, stackResultArea), (1, value))

  uint32_t offset() const { return offset_; }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmStackResult);
  }
};

// Represents a known-good derived pointer into an object or memory region (in
// the most general sense) that will not move while the derived pointer is live.
// The `offset` *must* be a valid offset into the object represented by `base`;
// hence overflow in the address calculation will never be an issue.  `offset`
// must be representable as a 31-bit unsigned integer.
//
// DO NOT use this with a base value of any JS-heap-resident object type.
// Such a value would need to be adjusted during GC, yet we have no mechanism
// to do that.  See bug 1810090.

class MWasmDerivedPointer : public MUnaryInstruction,
                            public NoTypePolicy::Data {
  MWasmDerivedPointer(MDefinition* base, size_t offset)
      : MUnaryInstruction(classOpcode, base), offset_(uint32_t(offset)) {
    MOZ_ASSERT(offset <= INT32_MAX);
    // Do not change this to allow `base` to be a GC-heap allocated type.
    MOZ_ASSERT(base->type() == MIRType::Pointer ||
               base->type() == TargetWordMIRType());
    setResultType(MIRType::Pointer);
    setMovable();
  }

  uint32_t offset_;

 public:
  INSTRUCTION_HEADER(WasmDerivedPointer)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, base))

  uint32_t offset() const { return offset_; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toWasmDerivedPointer()->offset() == offset();
  }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "(offs=%lld)", (long long int)offset_);
    extras->add(buf);
  }
#endif

  ALLOW_CLONE(MWasmDerivedPointer)
};

// As with MWasmDerivedPointer, DO NOT use this with a base value of any
// JS-heap-resident object type.
class MWasmDerivedIndexPointer : public MBinaryInstruction,
                                 public NoTypePolicy::Data {
  MWasmDerivedIndexPointer(MDefinition* base, MDefinition* index, Scale scale)
      : MBinaryInstruction(classOpcode, base, index), scale_(scale) {
    // Do not change this to allow `base` to be a GC-heap allocated type.
    MOZ_ASSERT(base->type() == MIRType::Pointer);
    setResultType(MIRType::Pointer);
    setMovable();
  }

  Scale scale_;

 public:
  INSTRUCTION_HEADER(WasmDerivedIndexPointer)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, base))
  NAMED_OPERANDS((1, index))

  Scale scale() const { return scale_; }

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toWasmDerivedIndexPointer()->scale() == scale();
  }

  ALLOW_CLONE(MWasmDerivedIndexPointer)
};

// Whether to perform a pre-write barrier for a wasm store reference.
enum class WasmPreBarrierKind : uint8_t { None, Normal };

// Stores a reference to an address. This performs a pre-barrier on the address,
// but not a post-barrier. A post-barrier must be performed separately, if it's
// required.  The accessed location is `valueBase + valueOffset`.  The latter
// must be be representable as a 31-bit unsigned integer.

class MWasmStoreRef : public MAryInstruction<3>, public NoTypePolicy::Data {
  uint32_t offset_;
  AliasSet::Flag aliasSet_;
  WasmPreBarrierKind preBarrierKind_;

  MWasmStoreRef(MDefinition* instance, MDefinition* valueBase,
                size_t valueOffset, MDefinition* value, AliasSet::Flag aliasSet,
                WasmPreBarrierKind preBarrierKind)
      : MAryInstruction<3>(classOpcode),
        offset_(uint32_t(valueOffset)),
        aliasSet_(aliasSet),
        preBarrierKind_(preBarrierKind) {
    MOZ_ASSERT(valueOffset <= INT32_MAX);
    MOZ_ASSERT(valueBase->type() == MIRType::Pointer ||
               valueBase->type() == MIRType::StackResults);
    MOZ_ASSERT(value->type() == MIRType::RefOrNull);
    initOperand(0, instance);
    initOperand(1, valueBase);
    initOperand(2, value);
  }

 public:
  INSTRUCTION_HEADER(WasmStoreRef)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance), (1, valueBase), (2, value))

  uint32_t offset() const { return offset_; }
  AliasSet getAliasSet() const override { return AliasSet::Store(aliasSet_); }
  WasmPreBarrierKind preBarrierKind() const { return preBarrierKind_; }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "(offs=%lld)", (long long int)offset_);
    extras->add(buf);
  }
#endif
};

// Given a value being written to another object, update the generational store
// buffer if the value is in the nursery and object is in the tenured heap.
class MWasmPostWriteBarrier : public MQuaternaryInstruction,
                              public NoTypePolicy::Data {
  uint32_t valueOffset_;

  MWasmPostWriteBarrier(MDefinition* instance, MDefinition* object,
                        MDefinition* valueBase, uint32_t valueOffset,
                        MDefinition* value)
      : MQuaternaryInstruction(classOpcode, instance, object, valueBase, value),
        valueOffset_(valueOffset) {
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(WasmPostWriteBarrier)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance), (1, object), (2, valueBase), (3, value))

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  uint32_t valueOffset() const { return valueOffset_; }

  ALLOW_CLONE(MWasmPostWriteBarrier)
};

class MWasmParameter : public MNullaryInstruction {
  ABIArg abi_;

  MWasmParameter(ABIArg abi, MIRType mirType)
      : MNullaryInstruction(classOpcode), abi_(abi) {
    setResultType(mirType);
  }

 public:
  INSTRUCTION_HEADER(WasmParameter)
  TRIVIAL_NEW_WRAPPERS

  ABIArg abi() const { return abi_; }
};

class MWasmReturn : public MAryControlInstruction<2, 0>,
                    public NoTypePolicy::Data {
  MWasmReturn(MDefinition* ins, MDefinition* instance)
      : MAryControlInstruction(classOpcode) {
    initOperand(0, ins);
    initOperand(1, instance);
  }

 public:
  INSTRUCTION_HEADER(WasmReturn)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MWasmReturnVoid : public MAryControlInstruction<1, 0>,
                        public NoTypePolicy::Data {
  explicit MWasmReturnVoid(MDefinition* instance)
      : MAryControlInstruction(classOpcode) {
    initOperand(0, instance);
  }

 public:
  INSTRUCTION_HEADER(WasmReturnVoid)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MWasmStackArg : public MUnaryInstruction, public NoTypePolicy::Data {
  MWasmStackArg(uint32_t spOffset, MDefinition* ins)
      : MUnaryInstruction(classOpcode, ins), spOffset_(spOffset) {}

  uint32_t spOffset_;

 public:
  INSTRUCTION_HEADER(WasmStackArg)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, arg))

  uint32_t spOffset() const { return spOffset_; }
  void incrementOffset(uint32_t inc) { spOffset_ += inc; }
};

template <typename Location>
class MWasmResultBase : public MNullaryInstruction {
  Location loc_;

 protected:
  MWasmResultBase(Opcode op, MIRType type, Location loc)
      : MNullaryInstruction(op), loc_(loc) {
    setResultType(type);
    setCallResultCapture();
  }

 public:
  Location loc() { return loc_; }
};

class MWasmRegisterResult : public MWasmResultBase<Register> {
  MWasmRegisterResult(MIRType type, Register reg)
      : MWasmResultBase(classOpcode, type, reg) {}

 public:
  INSTRUCTION_HEADER(WasmRegisterResult)
  TRIVIAL_NEW_WRAPPERS
};

class MWasmFloatRegisterResult : public MWasmResultBase<FloatRegister> {
  MWasmFloatRegisterResult(MIRType type, FloatRegister reg)
      : MWasmResultBase(classOpcode, type, reg) {}

 public:
  INSTRUCTION_HEADER(WasmFloatRegisterResult)
  TRIVIAL_NEW_WRAPPERS
};

class MWasmRegister64Result : public MWasmResultBase<Register64> {
  explicit MWasmRegister64Result(Register64 reg)
      : MWasmResultBase(classOpcode, MIRType::Int64, reg) {}

 public:
  INSTRUCTION_HEADER(WasmRegister64Result)
  TRIVIAL_NEW_WRAPPERS
};

class MWasmStackResultArea : public MNullaryInstruction {
 public:
  class StackResult {
    // Offset in bytes from lowest address of stack result area.
    uint32_t offset_;
    MIRType type_;

   public:
    StackResult() : type_(MIRType::Undefined) {}
    StackResult(uint32_t offset, MIRType type) : offset_(offset), type_(type) {}

    bool initialized() const { return type_ != MIRType::Undefined; }
    uint32_t offset() const {
      MOZ_ASSERT(initialized());
      return offset_;
    }
    MIRType type() const {
      MOZ_ASSERT(initialized());
      return type_;
    }
    uint32_t endOffset() const {
      return offset() + wasm::MIRTypeToABIResultSize(type());
    }
  };

 private:
  FixedList<StackResult> results_;
  uint32_t base_;

  explicit MWasmStackResultArea()
      : MNullaryInstruction(classOpcode), base_(UINT32_MAX) {
    setResultType(MIRType::StackResults);
  }

  void assertInitialized() const {
    MOZ_ASSERT(results_.length() != 0);
#ifdef DEBUG
    for (size_t i = 0; i < results_.length(); i++) {
      MOZ_ASSERT(results_[i].initialized());
    }
#endif
  }

  bool baseInitialized() const { return base_ != UINT32_MAX; }

 public:
  INSTRUCTION_HEADER(WasmStackResultArea)
  TRIVIAL_NEW_WRAPPERS

  [[nodiscard]] bool init(TempAllocator& alloc, size_t stackResultCount) {
    MOZ_ASSERT(results_.length() == 0);
    MOZ_ASSERT(stackResultCount > 0);
    if (!results_.init(alloc, stackResultCount)) {
      return false;
    }
    for (size_t n = 0; n < stackResultCount; n++) {
      results_[n] = StackResult();
    }
    return true;
  }

  size_t resultCount() const { return results_.length(); }
  const StackResult& result(size_t n) const {
    MOZ_ASSERT(results_[n].initialized());
    return results_[n];
  }
  void initResult(size_t n, const StackResult& loc) {
    MOZ_ASSERT(!results_[n].initialized());
    MOZ_ASSERT((n == 0) == (loc.offset() == 0));
    MOZ_ASSERT_IF(n > 0, loc.offset() >= result(n - 1).endOffset());
    results_[n] = loc;
  }

  uint32_t byteSize() const {
    assertInitialized();
    return result(resultCount() - 1).endOffset();
  }

  // Stack index indicating base of stack area.
  uint32_t base() const {
    MOZ_ASSERT(baseInitialized());
    return base_;
  }
  void setBase(uint32_t base) {
    MOZ_ASSERT(!baseInitialized());
    base_ = base;
    MOZ_ASSERT(baseInitialized());
  }
};

class MWasmStackResult : public MUnaryInstruction, public NoTypePolicy::Data {
  uint32_t resultIdx_;

  MWasmStackResult(MWasmStackResultArea* resultArea, size_t idx)
      : MUnaryInstruction(classOpcode, resultArea), resultIdx_(idx) {
    setResultType(result().type());
    setCallResultCapture();
  }

 public:
  INSTRUCTION_HEADER(WasmStackResult)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, resultArea))

  const MWasmStackResultArea::StackResult& result() const {
    return resultArea()->toWasmStackResultArea()->result(resultIdx_);
  }
};

// Arguments for constructing a catchable wasm call inside of a try block.
struct MWasmCallTryDesc {
  bool inTry;
  uint32_t relativeTryDepth;
  size_t tryNoteIndex;
  MBasicBlock* fallthroughBlock;
  MBasicBlock* prePadBlock;

  MWasmCallTryDesc()
      : inTry(false),
        relativeTryDepth(0),
        tryNoteIndex(0),
        fallthroughBlock(nullptr),
        prePadBlock(nullptr) {}
};

// Mixin class for wasm calls that may or may not be catchable.
class MWasmCallBase {
 public:
  struct Arg {
    AnyRegister reg;
    MDefinition* def;
    Arg(AnyRegister reg, MDefinition* def) : reg(reg), def(def) {}
  };
  typedef Vector<Arg, 8, SystemAllocPolicy> Args;

 protected:
  wasm::CallSiteDesc desc_;
  wasm::CalleeDesc callee_;
  wasm::FailureMode builtinMethodFailureMode_;
  FixedList<AnyRegister> argRegs_;
  uint32_t stackArgAreaSizeUnaligned_;
  ABIArg instanceArg_;
  bool inTry_;
  size_t tryNoteIndex_;

  MWasmCallBase(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
                uint32_t stackArgAreaSizeUnaligned, bool inTry,
                size_t tryNoteIndex)
      : desc_(desc),
        callee_(callee),
        builtinMethodFailureMode_(wasm::FailureMode::Infallible),
        stackArgAreaSizeUnaligned_(stackArgAreaSizeUnaligned),
        inTry_(inTry),
        tryNoteIndex_(tryNoteIndex) {}

  template <class MVariadicT>
  [[nodiscard]] bool initWithArgs(TempAllocator& alloc, MVariadicT* ins,
                                  const Args& args,
                                  MDefinition* tableIndexOrRef) {
    if (!argRegs_.init(alloc, args.length())) {
      return false;
    }
    for (size_t i = 0; i < argRegs_.length(); i++) {
      argRegs_[i] = args[i].reg;
    }

    if (!ins->init(alloc, argRegs_.length() + (tableIndexOrRef ? 1 : 0))) {
      return false;
    }
    // FixedList doesn't initialize its elements, so do an unchecked init.
    for (size_t i = 0; i < argRegs_.length(); i++) {
      ins->initOperand(i, args[i].def);
    }
    if (tableIndexOrRef) {
      ins->initOperand(argRegs_.length(), tableIndexOrRef);
    }
    return true;
  }

 public:
  static bool IsWasmCall(MDefinition* def) {
    return def->isWasmCallCatchable() || def->isWasmCallUncatchable();
  }

  size_t numArgs() const { return argRegs_.length(); }
  AnyRegister registerForArg(size_t index) const {
    MOZ_ASSERT(index < numArgs());
    return argRegs_[index];
  }
  const wasm::CallSiteDesc& desc() const { return desc_; }
  const wasm::CalleeDesc& callee() const { return callee_; }
  wasm::FailureMode builtinMethodFailureMode() const {
    MOZ_ASSERT(callee_.which() == wasm::CalleeDesc::BuiltinInstanceMethod);
    return builtinMethodFailureMode_;
  }
  uint32_t stackArgAreaSizeUnaligned() const {
    return stackArgAreaSizeUnaligned_;
  }

  const ABIArg& instanceArg() const { return instanceArg_; }

  bool inTry() const { return inTry_; }
  size_t tryNoteIndex() const { return tryNoteIndex_; }
};

// A wasm call that is catchable. This instruction is a control instruction,
// and terminates the block it is on. A normal return will proceed in a the
// fallthrough block. An exceptional return will unwind into the landing pad
// block for this call. The landing pad block must begin with an
// MWasmCallLandingPrePad.
class MWasmCallCatchable final : public MVariadicControlInstruction<2>,
                                 public MWasmCallBase,
                                 public NoTypePolicy::Data {
  MWasmCallCatchable(const wasm::CallSiteDesc& desc,
                     const wasm::CalleeDesc& callee,
                     uint32_t stackArgAreaSizeUnaligned, size_t tryNoteIndex)
      : MVariadicControlInstruction(classOpcode),
        MWasmCallBase(desc, callee, stackArgAreaSizeUnaligned, true,
                      tryNoteIndex) {}

 public:
  INSTRUCTION_HEADER(WasmCallCatchable)

  static MWasmCallCatchable* New(TempAllocator& alloc,
                                 const wasm::CallSiteDesc& desc,
                                 const wasm::CalleeDesc& callee,
                                 const Args& args,
                                 uint32_t stackArgAreaSizeUnaligned,
                                 const MWasmCallTryDesc& tryDesc,
                                 MDefinition* tableIndexOrRef = nullptr);

  bool possiblyCalls() const override { return true; }

  static const size_t FallthroughBranchIndex = 0;
  static const size_t PrePadBranchIndex = 1;
};

// A wasm call that is not catchable. This instruction is not a control
// instruction, and therefore is not a block terminator.
class MWasmCallUncatchable final : public MVariadicInstruction,
                                   public MWasmCallBase,
                                   public NoTypePolicy::Data {
  MWasmCallUncatchable(const wasm::CallSiteDesc& desc,
                       const wasm::CalleeDesc& callee,
                       uint32_t stackArgAreaSizeUnaligned)
      : MVariadicInstruction(classOpcode),
        MWasmCallBase(desc, callee, stackArgAreaSizeUnaligned, false, 0) {}

 public:
  INSTRUCTION_HEADER(WasmCallUncatchable)

  static MWasmCallUncatchable* New(TempAllocator& alloc,
                                   const wasm::CallSiteDesc& desc,
                                   const wasm::CalleeDesc& callee,
                                   const Args& args,
                                   uint32_t stackArgAreaSizeUnaligned,
                                   MDefinition* tableIndexOrRef = nullptr);

  static MWasmCallUncatchable* NewBuiltinInstanceMethodCall(
      TempAllocator& alloc, const wasm::CallSiteDesc& desc,
      const wasm::SymbolicAddress builtin, wasm::FailureMode failureMode,
      const ABIArg& instanceArg, const Args& args,
      uint32_t stackArgAreaSizeUnaligned);

  bool possiblyCalls() const override { return true; }
};

// A marker instruction for a block which is the landing pad for a catchable
// wasm call. This instruction does not emit any code, only filling in
// metadata. This instruction must be the first instruction added to the
// landing pad block.
class MWasmCallLandingPrePad : public MNullaryInstruction {
  // The block of the call that may unwind to this landing pad.
  MBasicBlock* callBlock_;
  // The index of the try note to initialize a landing pad for.
  size_t tryNoteIndex_;

  explicit MWasmCallLandingPrePad(MBasicBlock* callBlock, size_t tryNoteIndex)
      : MNullaryInstruction(classOpcode),
        callBlock_(callBlock),
        tryNoteIndex_(tryNoteIndex) {
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(WasmCallLandingPrePad)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  size_t tryNoteIndex() { return tryNoteIndex_; }
  MBasicBlock* callBlock() { return callBlock_; }
};

class MWasmSelect : public MTernaryInstruction, public NoTypePolicy::Data {
  MWasmSelect(MDefinition* trueExpr, MDefinition* falseExpr,
              MDefinition* condExpr)
      : MTernaryInstruction(classOpcode, trueExpr, falseExpr, condExpr) {
    MOZ_ASSERT(condExpr->type() == MIRType::Int32);
    MOZ_ASSERT(trueExpr->type() == falseExpr->type());
    setResultType(trueExpr->type());
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmSelect)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, trueExpr), (1, falseExpr), (2, condExpr))

  AliasSet getAliasSet() const override { return AliasSet::None(); }

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  ALLOW_CLONE(MWasmSelect)
};

class MWasmReinterpret : public MUnaryInstruction, public NoTypePolicy::Data {
  MWasmReinterpret(MDefinition* val, MIRType toType)
      : MUnaryInstruction(classOpcode, val) {
    switch (val->type()) {
      case MIRType::Int32:
        MOZ_ASSERT(toType == MIRType::Float32);
        break;
      case MIRType::Float32:
        MOZ_ASSERT(toType == MIRType::Int32);
        break;
      case MIRType::Double:
        MOZ_ASSERT(toType == MIRType::Int64);
        break;
      case MIRType::Int64:
        MOZ_ASSERT(toType == MIRType::Double);
        break;
      default:
        MOZ_CRASH("unexpected reinterpret conversion");
    }
    setMovable();
    setResultType(toType);
  }

 public:
  INSTRUCTION_HEADER(WasmReinterpret)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }

  ALLOW_CLONE(MWasmReinterpret)
};

class MRotate : public MBinaryInstruction, public NoTypePolicy::Data {
  bool isLeftRotate_;

  MRotate(MDefinition* input, MDefinition* count, MIRType type,
          bool isLeftRotate)
      : MBinaryInstruction(classOpcode, input, count),
        isLeftRotate_(isLeftRotate) {
    setMovable();
    setResultType(type);
    // Prevent reordering.  Although there's no problem eliding call result
    // definitions, there's also no need, as they cause no codegen.
    setGuard();
  }

 public:
  INSTRUCTION_HEADER(Rotate)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, input), (1, count))

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           ins->toRotate()->isLeftRotate() == isLeftRotate_;
  }

  bool isLeftRotate() const { return isLeftRotate_; }

  ALLOW_CLONE(MRotate)
};

// Wasm SIMD.
//
// See comment in WasmIonCompile.cpp for a justification for these nodes.

// (v128, v128, v128) -> v128 effect-free operation.
class MWasmTernarySimd128 : public MTernaryInstruction,
                            public NoTypePolicy::Data {
  wasm::SimdOp simdOp_;

  MWasmTernarySimd128(MDefinition* v0, MDefinition* v1, MDefinition* v2,
                      wasm::SimdOp simdOp)
      : MTernaryInstruction(classOpcode, v0, v1, v2), simdOp_(simdOp) {
    setMovable();
    setResultType(MIRType::Simd128);
  }

 public:
  INSTRUCTION_HEADER(WasmTernarySimd128)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, v0), (1, v1), (2, v2))

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
#ifdef ENABLE_WASM_SIMD
  MDefinition* foldsTo(TempAllocator& alloc) override;

  // If the control mask of a bitselect allows the operation to be specialized
  // as a shuffle and it is profitable to specialize it on this platform, return
  // true and the appropriate shuffle mask.
  bool specializeBitselectConstantMaskAsShuffle(int8_t shuffle[16]);
  // Checks if more relaxed version of lane select can be used. It returns true
  // if a bit mask input expected to be all 0s or 1s for entire 8-bit lanes,
  // false otherwise.
  bool canRelaxBitselect();
#endif

  wasm::SimdOp simdOp() const { return simdOp_; }

  ALLOW_CLONE(MWasmTernarySimd128)
};

// (v128, v128) -> v128 effect-free operations.
class MWasmBinarySimd128 : public MBinaryInstruction,
                           public NoTypePolicy::Data {
  wasm::SimdOp simdOp_;

  MWasmBinarySimd128(MDefinition* lhs, MDefinition* rhs, bool commutative,
                     wasm::SimdOp simdOp)
      : MBinaryInstruction(classOpcode, lhs, rhs), simdOp_(simdOp) {
    setMovable();
    setResultType(MIRType::Simd128);
    if (commutative) {
      setCommutative();
    }
  }

 public:
  INSTRUCTION_HEADER(WasmBinarySimd128)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return ins->toWasmBinarySimd128()->simdOp() == simdOp_ &&
           congruentIfOperandsEqual(ins);
  }
#ifdef ENABLE_WASM_SIMD
  MDefinition* foldsTo(TempAllocator& alloc) override;

  // Checks if pmaddubsw operation is supported.
  bool canPmaddubsw();
#endif

  wasm::SimdOp simdOp() const { return simdOp_; }

  // Platform-dependent specialization.
  bool specializeForConstantRhs();

  ALLOW_CLONE(MWasmBinarySimd128)
};

// (v128, const) -> v128 effect-free operations.
class MWasmBinarySimd128WithConstant : public MUnaryInstruction,
                                       public NoTypePolicy::Data {
  SimdConstant rhs_;
  wasm::SimdOp simdOp_;

  MWasmBinarySimd128WithConstant(MDefinition* lhs, const SimdConstant& rhs,
                                 wasm::SimdOp simdOp)
      : MUnaryInstruction(classOpcode, lhs), rhs_(rhs), simdOp_(simdOp) {
    setMovable();
    setResultType(MIRType::Simd128);
  }

 public:
  INSTRUCTION_HEADER(WasmBinarySimd128WithConstant)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return ins->toWasmBinarySimd128WithConstant()->simdOp() == simdOp_ &&
           congruentIfOperandsEqual(ins) &&
           rhs_.bitwiseEqual(ins->toWasmBinarySimd128WithConstant()->rhs());
  }

  wasm::SimdOp simdOp() const { return simdOp_; }
  MDefinition* lhs() const { return input(); }
  const SimdConstant& rhs() const { return rhs_; }

  ALLOW_CLONE(MWasmBinarySimd128WithConstant)
};

// (v128, scalar, imm) -> v128 effect-free operations.
class MWasmReplaceLaneSimd128 : public MBinaryInstruction,
                                public NoTypePolicy::Data {
  uint32_t laneIndex_;
  wasm::SimdOp simdOp_;

  MWasmReplaceLaneSimd128(MDefinition* lhs, MDefinition* rhs,
                          uint32_t laneIndex, wasm::SimdOp simdOp)
      : MBinaryInstruction(classOpcode, lhs, rhs),
        laneIndex_(laneIndex),
        simdOp_(simdOp) {
    setMovable();
    setResultType(MIRType::Simd128);
  }

 public:
  INSTRUCTION_HEADER(WasmReplaceLaneSimd128)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return ins->toWasmReplaceLaneSimd128()->simdOp() == simdOp_ &&
           ins->toWasmReplaceLaneSimd128()->laneIndex() == laneIndex_ &&
           congruentIfOperandsEqual(ins);
  }

  uint32_t laneIndex() const { return laneIndex_; }
  wasm::SimdOp simdOp() const { return simdOp_; }

  ALLOW_CLONE(MWasmReplaceLaneSimd128)
};

// (scalar) -> v128 effect-free operations.
class MWasmScalarToSimd128 : public MUnaryInstruction,
                             public NoTypePolicy::Data {
  wasm::SimdOp simdOp_;

  MWasmScalarToSimd128(MDefinition* src, wasm::SimdOp simdOp)
      : MUnaryInstruction(classOpcode, src), simdOp_(simdOp) {
    setMovable();
    setResultType(MIRType::Simd128);
  }

 public:
  INSTRUCTION_HEADER(WasmScalarToSimd128)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return ins->toWasmScalarToSimd128()->simdOp() == simdOp_ &&
           congruentIfOperandsEqual(ins);
  }
#ifdef ENABLE_WASM_SIMD
  MDefinition* foldsTo(TempAllocator& alloc) override;
#endif

  wasm::SimdOp simdOp() const { return simdOp_; }

  ALLOW_CLONE(MWasmScalarToSimd128)
};

// (v128, imm) -> scalar effect-free operations.
class MWasmReduceSimd128 : public MUnaryInstruction, public NoTypePolicy::Data {
  wasm::SimdOp simdOp_;
  uint32_t imm_;

  MWasmReduceSimd128(MDefinition* src, wasm::SimdOp simdOp, MIRType outType,
                     uint32_t imm)
      : MUnaryInstruction(classOpcode, src), simdOp_(simdOp), imm_(imm) {
    setMovable();
    setResultType(outType);
  }

 public:
  INSTRUCTION_HEADER(WasmReduceSimd128)
  TRIVIAL_NEW_WRAPPERS

  AliasSet getAliasSet() const override { return AliasSet::None(); }
  bool congruentTo(const MDefinition* ins) const override {
    return ins->toWasmReduceSimd128()->simdOp() == simdOp_ &&
           ins->toWasmReduceSimd128()->imm() == imm_ &&
           congruentIfOperandsEqual(ins);
  }
#ifdef ENABLE_WASM_SIMD
  MDefinition* foldsTo(TempAllocator& alloc) override;
#endif

  uint32_t imm() const { return imm_; }
  wasm::SimdOp simdOp() const { return simdOp_; }

  ALLOW_CLONE(MWasmReduceSimd128)
};

class MWasmLoadLaneSimd128
    : public MVariadicInstruction,  // memoryBase is nullptr on some platforms
      public NoTypePolicy::Data {
  wasm::MemoryAccessDesc access_;
  uint32_t laneSize_;
  uint32_t laneIndex_;
  uint32_t memoryBaseIndex_;

  MWasmLoadLaneSimd128(const wasm::MemoryAccessDesc& access, uint32_t laneSize,
                       uint32_t laneIndex, uint32_t memoryBaseIndex)
      : MVariadicInstruction(classOpcode),
        access_(access),
        laneSize_(laneSize),
        laneIndex_(laneIndex),
        memoryBaseIndex_(memoryBaseIndex) {
    MOZ_ASSERT(!access_.isAtomic());
    setGuard();
    setResultType(MIRType::Simd128);
  }

 public:
  INSTRUCTION_HEADER(WasmLoadLaneSimd128)
  NAMED_OPERANDS((0, base), (1, value));

  static MWasmLoadLaneSimd128* New(TempAllocator& alloc,
                                   MDefinition* memoryBase, MDefinition* base,
                                   const wasm::MemoryAccessDesc& access,
                                   uint32_t laneSize, uint32_t laneIndex,
                                   MDefinition* value) {
    uint32_t nextIndex = 2;
    uint32_t memoryBaseIndex = memoryBase ? nextIndex++ : UINT32_MAX;

    MWasmLoadLaneSimd128* load = new (alloc)
        MWasmLoadLaneSimd128(access, laneSize, laneIndex, memoryBaseIndex);
    if (!load->init(alloc, nextIndex)) {
      return nullptr;
    }

    load->initOperand(0, base);
    load->initOperand(1, value);
    if (memoryBase) {
      load->initOperand(memoryBaseIndex, memoryBase);
    }

    return load;
  }

  const wasm::MemoryAccessDesc& access() const { return access_; }
  uint32_t laneSize() const { return laneSize_; }
  uint32_t laneIndex() const { return laneIndex_; }
  bool hasMemoryBase() const { return memoryBaseIndex_ != UINT32_MAX; }
  MDefinition* memoryBase() const {
    MOZ_ASSERT(hasMemoryBase());
    return getOperand(memoryBaseIndex_);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Load(AliasSet::WasmHeap);
  }
};

class MWasmStoreLaneSimd128 : public MVariadicInstruction,
                              public NoTypePolicy::Data {
  wasm::MemoryAccessDesc access_;
  uint32_t laneSize_;
  uint32_t laneIndex_;
  uint32_t memoryBaseIndex_;

  explicit MWasmStoreLaneSimd128(const wasm::MemoryAccessDesc& access,
                                 uint32_t laneSize, uint32_t laneIndex,
                                 uint32_t memoryBaseIndex)
      : MVariadicInstruction(classOpcode),
        access_(access),
        laneSize_(laneSize),
        laneIndex_(laneIndex),
        memoryBaseIndex_(memoryBaseIndex) {
    MOZ_ASSERT(!access_.isAtomic());
    setGuard();
    setResultType(MIRType::Simd128);
  }

 public:
  INSTRUCTION_HEADER(WasmStoreLaneSimd128)
  NAMED_OPERANDS((0, base), (1, value))

  static MWasmStoreLaneSimd128* New(TempAllocator& alloc,
                                    MDefinition* memoryBase, MDefinition* base,
                                    const wasm::MemoryAccessDesc& access,
                                    uint32_t laneSize, uint32_t laneIndex,
                                    MDefinition* value) {
    uint32_t nextIndex = 2;
    uint32_t memoryBaseIndex = memoryBase ? nextIndex++ : UINT32_MAX;

    MWasmStoreLaneSimd128* store = new (alloc)
        MWasmStoreLaneSimd128(access, laneSize, laneIndex, memoryBaseIndex);
    if (!store->init(alloc, nextIndex)) {
      return nullptr;
    }

    store->initOperand(0, base);
    store->initOperand(1, value);
    if (memoryBase) {
      store->initOperand(memoryBaseIndex, memoryBase);
    }

    return store;
  }

  const wasm::MemoryAccessDesc& access() const { return access_; }
  uint32_t laneSize() const { return laneSize_; }
  uint32_t laneIndex() const { return laneIndex_; }
  bool hasMemoryBase() const { return memoryBaseIndex_ != UINT32_MAX; }
  MDefinition* memoryBase() const {
    MOZ_ASSERT(hasMemoryBase());
    return getOperand(memoryBaseIndex_);
  }

  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::WasmHeap);
  }
};

// End Wasm SIMD

// Used by MIR building to represent the bytecode result of an operation for
// which an MBail was generated, to balance the basic block's MDefinition stack.
class MUnreachableResult : public MNullaryInstruction {
  explicit MUnreachableResult(MIRType type) : MNullaryInstruction(classOpcode) {
    MOZ_ASSERT(type != MIRType::None);
    setResultType(type);
  }

 public:
  INSTRUCTION_HEADER(UnreachableResult)
  TRIVIAL_NEW_WRAPPERS

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins);
  }
  AliasSet getAliasSet() const override { return AliasSet::None(); }
};

class MIonToWasmCall final : public MVariadicInstruction,
                             public NoTypePolicy::Data {
  CompilerGCPointer<WasmInstanceObject*> instanceObj_;
  const wasm::FuncExport& funcExport_;

  MIonToWasmCall(WasmInstanceObject* instanceObj, MIRType resultType,
                 const wasm::FuncExport& funcExport)
      : MVariadicInstruction(classOpcode),
        instanceObj_(instanceObj),
        funcExport_(funcExport) {
    setResultType(resultType);
  }

 public:
  INSTRUCTION_HEADER(IonToWasmCall);

  static MIonToWasmCall* New(TempAllocator& alloc,
                             WasmInstanceObject* instanceObj,
                             const wasm::FuncExport& funcExport);

  void initArg(size_t i, MDefinition* arg) { initOperand(i, arg); }

  WasmInstanceObject* instanceObject() const { return instanceObj_; }
  wasm::Instance* instance() const { return &instanceObj_->instance(); }
  const wasm::FuncExport& funcExport() const { return funcExport_; }
  bool possiblyCalls() const override { return true; }
#ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override;
#endif
};

// For accesses to wasm object fields, we need to be able to describe 8- and
// 16-bit accesses.  But MIRType can't represent those.  Hence these two
// supplemental enums, used for reading and writing fields respectively.

// Indicates how to widen an 8- or 16-bit value (when it is read from memory).
enum class MWideningOp : uint8_t { None, FromU16, FromS16, FromU8, FromS8 };

#ifdef JS_JITSPEW
static inline const char* StringFromMWideningOp(MWideningOp op) {
  switch (op) {
    case MWideningOp::None:
      return "None";
    case MWideningOp::FromU16:
      return "FromU16";
    case MWideningOp::FromS16:
      return "FromS16";
    case MWideningOp::FromU8:
      return "FromU8";
    case MWideningOp::FromS8:
      return "FromS8";
    default:
      break;
  }
  MOZ_CRASH("Unknown MWideningOp");
}
#endif

// Indicates how to narrow a 32-bit value (when it is written to memory).  The
// operation is a simple truncate.
enum class MNarrowingOp : uint8_t { None, To16, To8 };

#ifdef JS_JITSPEW
static inline const char* StringFromMNarrowingOp(MNarrowingOp op) {
  switch (op) {
    case MNarrowingOp::None:
      return "None";
    case MNarrowingOp::To16:
      return "To16";
    case MNarrowingOp::To8:
      return "To8";
    default:
      break;
  }
  MOZ_CRASH("Unknown MNarrowingOp");
}
#endif

// Provide information about potential trap at the instruction machine code,
// e.g. null pointer dereference.
struct TrapSiteInfo {
  wasm::BytecodeOffset offset;
  explicit TrapSiteInfo(wasm::BytecodeOffset offset_) : offset(offset_) {}
};

typedef mozilla::Maybe<TrapSiteInfo> MaybeTrapSiteInfo;

// Load an object field stored at a fixed offset from a base pointer.  This
// field may be any value type, including references.  No barriers are
// performed.  The offset must be representable as a 31-bit unsigned integer.
class MWasmLoadField : public MUnaryInstruction, public NoTypePolicy::Data {
  uint32_t offset_;
  MWideningOp wideningOp_;
  AliasSet aliases_;
  MaybeTrapSiteInfo maybeTrap_;

  MWasmLoadField(MDefinition* obj, uint32_t offset, MIRType type,
                 MWideningOp wideningOp, AliasSet aliases,
                 MaybeTrapSiteInfo maybeTrap = mozilla::Nothing())
      : MUnaryInstruction(classOpcode, obj),
        offset_(uint32_t(offset)),
        wideningOp_(wideningOp),
        aliases_(aliases),
        maybeTrap_(maybeTrap) {
    MOZ_ASSERT(offset <= INT32_MAX);
    // "if you want to widen the value when it is loaded, the destination type
    // must be Int32".
    MOZ_ASSERT_IF(wideningOp != MWideningOp::None, type == MIRType::Int32);
    MOZ_ASSERT(
        aliases.flags() ==
            AliasSet::Load(AliasSet::WasmStructOutlineDataPointer).flags() ||
        aliases.flags() ==
            AliasSet::Load(AliasSet::WasmArrayNumElements).flags() ||
        aliases.flags() ==
            AliasSet::Load(AliasSet::WasmArrayDataPointer).flags() ||
        aliases.flags() == AliasSet::Load(AliasSet::Any).flags());
    setResultType(type);
    if (maybeTrap_) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(WasmLoadField)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, obj))

  uint32_t offset() const { return offset_; }
  MWideningOp wideningOp() const { return wideningOp_; }
  AliasSet getAliasSet() const override { return aliases_; }
  MaybeTrapSiteInfo maybeTrap() const { return maybeTrap_; }

  bool congruentTo(const MDefinition* ins) const override {
    // In the limited case where this insn is used to read
    // WasmStructObject::outlineData_ (the field itself, not what it points
    // at), we allow commoning up to happen.  This is OK because
    // WasmStructObject::outlineData_ is readonly for the life of the
    // WasmStructObject.
    if (!ins->isWasmLoadField()) {
      return false;
    }
    const MWasmLoadField* other = ins->toWasmLoadField();
    return ins->isWasmLoadField() && congruentIfOperandsEqual(ins) &&
           offset() == other->offset() && wideningOp() == other->wideningOp() &&
           getAliasSet().flags() == other->getAliasSet().flags() &&
           getAliasSet().flags() ==
               AliasSet::Load(AliasSet::WasmStructOutlineDataPointer).flags();
  }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[96];
    SprintfLiteral(buf, "(offs=%lld, wideningOp=%s)", (long long int)offset_,
                   StringFromMWideningOp(wideningOp_));
    extras->add(buf);
  }
#endif
};

// Loads a value from a location, denoted as a fixed offset from a base
// pointer, which (it is assumed) is within a wasm object.  This field may be
// any value type, including references.  No barriers are performed.
//
// This instruction takes a pointer to a second object `ka`, which it is
// necessary to keep alive.  It is expected that `ka` holds a reference to
// `obj`, but this is not enforced and no code is generated to access `ka`.
// This instruction extends the lifetime of `ka` so that it, and hence `obj`,
// cannot be collected while `obj` is live.  This is necessary if `obj` does
// not point to a GC-managed object.  `offset` must be representable as a
// 31-bit unsigned integer.
class MWasmLoadFieldKA : public MBinaryInstruction, public NoTypePolicy::Data {
  uint32_t offset_;
  MWideningOp wideningOp_;
  AliasSet aliases_;
  MaybeTrapSiteInfo maybeTrap_;

  MWasmLoadFieldKA(MDefinition* ka, MDefinition* obj, size_t offset,
                   MIRType type, MWideningOp wideningOp, AliasSet aliases,
                   MaybeTrapSiteInfo maybeTrap = mozilla::Nothing())
      : MBinaryInstruction(classOpcode, ka, obj),
        offset_(uint32_t(offset)),
        wideningOp_(wideningOp),
        aliases_(aliases),
        maybeTrap_(maybeTrap) {
    MOZ_ASSERT(offset <= INT32_MAX);
    MOZ_ASSERT_IF(wideningOp != MWideningOp::None, type == MIRType::Int32);
    MOZ_ASSERT(
        aliases.flags() ==
            AliasSet::Load(AliasSet::WasmStructInlineDataArea).flags() ||
        aliases.flags() ==
            AliasSet::Load(AliasSet::WasmStructOutlineDataArea).flags() ||
        aliases.flags() ==
            AliasSet::Load(AliasSet::WasmArrayDataArea).flags() ||
        aliases.flags() == AliasSet::Load(AliasSet::Any).flags());
    setResultType(type);
    if (maybeTrap_) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(WasmLoadFieldKA)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, ka), (1, obj))

  uint32_t offset() const { return offset_; }
  MWideningOp wideningOp() const { return wideningOp_; }
  AliasSet getAliasSet() const override { return aliases_; }
  MaybeTrapSiteInfo maybeTrap() const { return maybeTrap_; }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[96];
    SprintfLiteral(buf, "(offs=%lld, wideningOp=%s)", (long long int)offset_,
                   StringFromMWideningOp(wideningOp_));
    extras->add(buf);
  }
#endif
};

// Stores a non-reference value to anlocation, denoted as a fixed offset from
// a base pointer, which (it is assumed) is within a wasm object.  This field
// may be any value type, _excluding_ references.  References _must_ use the
// 'Ref' variant of this instruction.  The offset must be representable as a
// 31-bit unsigned integer.
//
// This instruction takes a second object `ka` that must be kept alive, as
// described for MWasmLoadFieldKA above.
class MWasmStoreFieldKA : public MTernaryInstruction,
                          public NoTypePolicy::Data {
  uint32_t offset_;
  MNarrowingOp narrowingOp_;
  AliasSet aliases_;
  MaybeTrapSiteInfo maybeTrap_;

  MWasmStoreFieldKA(MDefinition* ka, MDefinition* obj, size_t offset,
                    MDefinition* value, MNarrowingOp narrowingOp,
                    AliasSet aliases,
                    MaybeTrapSiteInfo maybeTrap = mozilla::Nothing())
      : MTernaryInstruction(classOpcode, ka, obj, value),
        offset_(uint32_t(offset)),
        narrowingOp_(narrowingOp),
        aliases_(aliases),
        maybeTrap_(maybeTrap) {
    MOZ_ASSERT(offset <= INT32_MAX);
    MOZ_ASSERT(value->type() != MIRType::RefOrNull);
    // "if you want to narrow the value when it is stored, the source type
    // must be Int32".
    MOZ_ASSERT_IF(narrowingOp != MNarrowingOp::None,
                  value->type() == MIRType::Int32);
    MOZ_ASSERT(
        aliases.flags() ==
            AliasSet::Store(AliasSet::WasmStructInlineDataArea).flags() ||
        aliases.flags() ==
            AliasSet::Store(AliasSet::WasmStructOutlineDataArea).flags() ||
        aliases.flags() ==
            AliasSet::Store(AliasSet::WasmArrayDataArea).flags() ||
        aliases.flags() == AliasSet::Store(AliasSet::Any).flags());
    if (maybeTrap_) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(WasmStoreFieldKA)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, ka), (1, obj), (2, value))

  uint32_t offset() const { return offset_; }
  MNarrowingOp narrowingOp() const { return narrowingOp_; }
  AliasSet getAliasSet() const override { return aliases_; }
  MaybeTrapSiteInfo maybeTrap() const { return maybeTrap_; }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[96];
    SprintfLiteral(buf, "(offs=%lld, narrowingOp=%s)", (long long int)offset_,
                   StringFromMNarrowingOp(narrowingOp_));
    extras->add(buf);
  }
#endif
};

// Stores a reference value to a location, denoted as a fixed offset from a
// base pointer, which (it is assumed) is within a wasm object.  This
// instruction emits a pre-barrier.  A post barrier _must_ be performed
// separately.  The offset must be representable as a 31-bit unsigned integer.
//
// This instruction takes a second object `ka` that must be kept alive, as
// described for MWasmLoadFieldKA above.
class MWasmStoreFieldRefKA : public MAryInstruction<4>,
                             public NoTypePolicy::Data {
  uint32_t offset_;
  AliasSet aliases_;
  MaybeTrapSiteInfo maybeTrap_;
  WasmPreBarrierKind preBarrierKind_;

  MWasmStoreFieldRefKA(MDefinition* instance, MDefinition* ka, MDefinition* obj,
                       size_t offset, MDefinition* value, AliasSet aliases,
                       MaybeTrapSiteInfo maybeTrap,
                       WasmPreBarrierKind preBarrierKind)
      : MAryInstruction<4>(classOpcode),
        offset_(uint32_t(offset)),
        aliases_(aliases),
        maybeTrap_(maybeTrap),
        preBarrierKind_(preBarrierKind) {
    MOZ_ASSERT(obj->type() == TargetWordMIRType() ||
               obj->type() == MIRType::Pointer ||
               obj->type() == MIRType::RefOrNull);
    MOZ_ASSERT(offset <= INT32_MAX);
    MOZ_ASSERT(value->type() == MIRType::RefOrNull);
    MOZ_ASSERT(
        aliases.flags() ==
            AliasSet::Store(AliasSet::WasmStructInlineDataArea).flags() ||
        aliases.flags() ==
            AliasSet::Store(AliasSet::WasmStructOutlineDataArea).flags() ||
        aliases.flags() ==
            AliasSet::Store(AliasSet::WasmArrayDataArea).flags() ||
        aliases.flags() == AliasSet::Store(AliasSet::Any).flags());
    initOperand(0, instance);
    initOperand(1, ka);
    initOperand(2, obj);
    initOperand(3, value);
    if (maybeTrap_) {
      setGuard();
    }
  }

 public:
  INSTRUCTION_HEADER(WasmStoreFieldRefKA)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, instance), (1, ka), (2, obj), (3, value))

  uint32_t offset() const { return offset_; }
  AliasSet getAliasSet() const override { return aliases_; }
  MaybeTrapSiteInfo maybeTrap() const { return maybeTrap_; }
  WasmPreBarrierKind preBarrierKind() const { return preBarrierKind_; }

#ifdef JS_JITSPEW
  void getExtras(ExtrasCollector* extras) override {
    char buf[64];
    SprintfLiteral(buf, "(offs=%lld)", (long long int)offset_);
    extras->add(buf);
  }
#endif
};

class MWasmGcObjectIsSubtypeOfAbstract : public MUnaryInstruction,
                                         public NoTypePolicy::Data {
  wasm::RefType sourceType_;
  wasm::RefType destType_;

  MWasmGcObjectIsSubtypeOfAbstract(MDefinition* object,
                                   wasm::RefType sourceType,
                                   wasm::RefType destType)
      : MUnaryInstruction(classOpcode, object),
        sourceType_(sourceType),
        destType_(destType) {
    MOZ_ASSERT(!destType.isTypeRef());
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmGcObjectIsSubtypeOfAbstract)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object))

  wasm::RefType sourceType() const { return sourceType_; };
  wasm::RefType destType() const { return destType_; };

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           sourceType() ==
               ins->toWasmGcObjectIsSubtypeOfAbstract()->sourceType() &&
           destType() == ins->toWasmGcObjectIsSubtypeOfAbstract()->destType();
  }

  HashNumber valueHash() const override {
    HashNumber hn = MUnaryInstruction::valueHash();
    hn = addU64ToHash(hn, sourceType().packed().bits());
    hn = addU64ToHash(hn, destType().packed().bits());
    return hn;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
};

// Tests if the WasmGcObject, `object`, is a subtype of `superSuperTypeVector`.
// The actual super type definition must be known at compile time, so that the
// subtyping depth of super type depth can be used.
class MWasmGcObjectIsSubtypeOfConcrete : public MBinaryInstruction,
                                         public NoTypePolicy::Data {
  wasm::RefType sourceType_;
  wasm::RefType destType_;

  MWasmGcObjectIsSubtypeOfConcrete(MDefinition* object,
                                   MDefinition* superSuperTypeVector,
                                   wasm::RefType sourceType,
                                   wasm::RefType destType)
      : MBinaryInstruction(classOpcode, object, superSuperTypeVector),
        sourceType_(sourceType),
        destType_(destType) {
    MOZ_ASSERT(destType.isTypeRef());
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(WasmGcObjectIsSubtypeOfConcrete)
  TRIVIAL_NEW_WRAPPERS
  NAMED_OPERANDS((0, object), (1, superSuperTypeVector))

  wasm::RefType sourceType() const { return sourceType_; };
  wasm::RefType destType() const { return destType_; };

  bool congruentTo(const MDefinition* ins) const override {
    return congruentIfOperandsEqual(ins) &&
           sourceType() ==
               ins->toWasmGcObjectIsSubtypeOfConcrete()->sourceType() &&
           destType() == ins->toWasmGcObjectIsSubtypeOfConcrete()->destType();
  }

  HashNumber valueHash() const override {
    HashNumber hn = MBinaryInstruction::valueHash();
    hn = addU64ToHash(hn, sourceType().packed().bits());
    hn = addU64ToHash(hn, destType().packed().bits());
    return hn;
  }

  MDefinition* foldsTo(TempAllocator& alloc) override;
};

#ifdef FUZZING_JS_FUZZILLI
class MFuzzilliHash : public MUnaryInstruction, public NoTypePolicy::Data {
  explicit MFuzzilliHash(MDefinition* obj)
      : MUnaryInstruction(classOpcode, obj) {
    setResultType(MIRType::Int32);
    setMovable();
  }

 public:
  INSTRUCTION_HEADER(FuzzilliHash);
  TRIVIAL_NEW_WRAPPERS
  ALLOW_CLONE(MFuzzilliHash)

#  ifdef DEBUG
  bool isConsistentFloat32Use(MUse* use) const override { return true; }
#  endif

  AliasSet getAliasSet() const override {
    MDefinition* obj = getOperand(0);
    if (obj->type() == MIRType::Object || obj->type() == MIRType::Value) {
      return AliasSet::Load(AliasSet::ObjectFields | AliasSet::FixedSlot |
                            AliasSet::DynamicSlot | AliasSet::Element |
                            AliasSet::UnboxedElement);
    }
    return AliasSet::None();
  }
};

class MFuzzilliHashStore : public MUnaryInstruction, public NoTypePolicy::Data {
  explicit MFuzzilliHashStore(MDefinition* obj)
      : MUnaryInstruction(classOpcode, obj) {
    MOZ_ASSERT(obj->type() == MIRType::Int32);

    setResultType(MIRType::None);
  }

 public:
  INSTRUCTION_HEADER(FuzzilliHashStore);
  TRIVIAL_NEW_WRAPPERS
  ALLOW_CLONE(MFuzzilliHashStore)

  // this is a store and hence effectful, however no other load can
  // alias with the store
  AliasSet getAliasSet() const override {
    return AliasSet::Store(AliasSet::FuzzilliHash);
  }
};
#endif

#undef INSTRUCTION_HEADER

void MUse::init(MDefinition* producer, MNode* consumer) {
  MOZ_ASSERT(!consumer_, "Initializing MUse that already has a consumer");
  MOZ_ASSERT(!producer_, "Initializing MUse that already has a producer");
  initUnchecked(producer, consumer);
}

void MUse::initUnchecked(MDefinition* producer, MNode* consumer) {
  MOZ_ASSERT(consumer, "Initializing to null consumer");
  consumer_ = consumer;
  producer_ = producer;
  producer_->addUseUnchecked(this);
}

void MUse::initUncheckedWithoutProducer(MNode* consumer) {
  MOZ_ASSERT(consumer, "Initializing to null consumer");
  consumer_ = consumer;
  producer_ = nullptr;
}

void MUse::replaceProducer(MDefinition* producer) {
  MOZ_ASSERT(consumer_, "Resetting MUse without a consumer");
  producer_->removeUse(this);
  producer_ = producer;
  producer_->addUse(this);
}

void MUse::releaseProducer() {
  MOZ_ASSERT(consumer_, "Clearing MUse without a consumer");
  producer_->removeUse(this);
  producer_ = nullptr;
}

// Implement cast functions now that the compiler can see the inheritance.

MDefinition* MNode::toDefinition() {
  MOZ_ASSERT(isDefinition());
  return (MDefinition*)this;
}

MResumePoint* MNode::toResumePoint() {
  MOZ_ASSERT(isResumePoint());
  return (MResumePoint*)this;
}

MInstruction* MDefinition::toInstruction() {
  MOZ_ASSERT(!isPhi());
  return (MInstruction*)this;
}

const MInstruction* MDefinition::toInstruction() const {
  MOZ_ASSERT(!isPhi());
  return (const MInstruction*)this;
}

MControlInstruction* MDefinition::toControlInstruction() {
  MOZ_ASSERT(isControlInstruction());
  return (MControlInstruction*)this;
}

MConstant* MDefinition::maybeConstantValue() {
  MDefinition* op = this;
  if (op->isBox()) {
    op = op->toBox()->input();
  }
  if (op->isConstant()) {
    return op->toConstant();
  }
  return nullptr;
}

#ifdef ENABLE_WASM_SIMD
MWasmShuffleSimd128* BuildWasmShuffleSimd128(TempAllocator& alloc,
                                             const int8_t* control,
                                             MDefinition* lhs,
                                             MDefinition* rhs);
#endif  // ENABLE_WASM_SIMD

}  // namespace jit
}  // namespace js

#endif /* jit_MIR_h */
