// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_COMPILER_TURBOSHAFT_OPERATIONS_H_
#define V8_COMPILER_TURBOSHAFT_OPERATIONS_H_

#include <cmath>
#include <cstdint>
#include <cstring>
#include <limits>
#include <optional>
#include <tuple>
#include <type_traits>
#include <utility>

#include "src/base/logging.h"
#include "src/base/macros.h"
#include "src/base/platform/mutex.h"
#include "src/base/small-vector.h"
#include "src/base/string-format.h"
#include "src/base/strong-alias.h"
#include "src/base/template-utils.h"
#include "src/base/vector.h"
#include "src/codegen/external-reference.h"
#include "src/common/globals.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/globals.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/turboshaft/deopt-data.h"
#include "src/compiler/turboshaft/fast-hash.h"
#include "src/compiler/turboshaft/index.h"
#include "src/compiler/turboshaft/representations.h"
#include "src/compiler/turboshaft/snapshot-table.h"
#include "src/compiler/turboshaft/types.h"
#include "src/compiler/turboshaft/utils.h"
#include "src/compiler/turboshaft/zone-with-name.h"
#include "src/compiler/write-barrier-kind.h"
#include "src/flags/flags.h"
#include "src/maglev/maglev-node-type.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/js-objects.h"
#include "src/wasm/effect-handler.h"

#if V8_ENABLE_WEBASSEMBLY
#include "src/wasm/wasm-objects.h"
#endif

namespace v8::internal {
class HeapObject;
}  // namespace v8::internal
namespace v8::internal::compiler {
class CallDescriptor;
class NameRef;
class WeakHomomorphicFixedArrayRef;
class JSWasmCallParameters;
class DeoptimizeParameters;
class FrameStateInfo;
class Node;
enum class TrapId : int32_t;
}  // namespace v8::internal::compiler
namespace v8::internal::compiler::turboshaft {

inline constexpr char kCompilationZoneName[] = "compilation-zone";

class Block;
struct FrameStateData;
class Graph;
struct FrameStateOp;

enum class HashingStrategy {
  kDefault,
  // This strategy requires that hashing a graph during builtin construction
  // (mksnapshot) produces the same hash for repeated runs of mksnapshot. This
  // requires that no pointers and external constants are used in hashes.
  kMakeSnapshotStable,
};

// This belongs to `VariableReducer` in `variable-reducer.h`. It is defined here
// because of cyclic header dependencies.
struct VariableData {
  MaybeRegisterRepresentation rep;
  bool loop_invariant;
  IntrusiveSetIndex active_loop_variables_index = {};
};
using Variable = SnapshotTable<OpIndex, VariableData>::Key;

// DEFINING NEW OPERATIONS
// =======================
// For each operation `Foo`, we define:
// - An entry V(Foo) in one of the TURBOSHAFT*OPERATION list (eg,
//   TURBOSHAFT_OPERATION_LIST_BLOCK_TERMINATOR,
//   TURBOSHAFT_SIMPLIFIED_OPERATION_LIST etc), which defines
//   `Opcode::kFoo` and whether the operation is a block terminator.
// - A `struct FooOp`, which derives from either `OperationT<FooOp>` or
//   `FixedArityOperationT<k, FooOp>` if the op always has excactly `k` inputs.
// Furthermore, the struct has to contain:
// - A bunch of options directly as public fields.
// - A getter `options()` returning a tuple of all these options. This is used
//   for default printing and hashing. Alternatively, `void
//   PrintOptions(std::ostream& os) const` and `size_t hash_value() const` can
//   also be defined manually.
// - Getters for named inputs.
// - A constructor that first takes all the inputs and then all the options. For
//   a variable arity operation where the constructor doesn't take the inputs as
//   a single base::Vector<OpIndex> argument, it's also necessary to overwrite
//   the static `New` function, see `CallOp` for an example.
// - An `Explode` method that unpacks an operation and invokes the passed
//   callback. If the operation inherits from FixedArityOperationT, the base
//   class already provides the required implementation.
// - `OpEffects` as either a static constexpr member `effects` or a
//   non-static method `Effects()` if the effects depend on the particular
//   operation and not just the opcode.
// - outputs_rep/inputs_rep methods, which should return a vector describing the
//   representation of the outputs and inputs of this operations.
// After defining the struct here, you'll also need to integrate it in
// Turboshaft:
// - If Foo is not lowered before reaching the instruction selector, handle
//   Opcode::kFoo in the Turboshaft VisitNode of instruction-selector.cc.

#ifdef V8_ENABLE_WEBASSEMBLY
// These operations should be lowered to Machine operations during
// WasmLoweringPhase.
#define TURBOSHAFT_WASM_OPERATION_LIST(V) \
  V(WasmStackCheck)                       \
  V(WasmIncCoverageCounter)               \
  V(GlobalGet)                            \
  V(GlobalSet)                            \
  V(RootConstant)                         \
  V(IsRootConstant)                       \
  V(Null)                                 \
  V(IsNull)                               \
  V(AssertNotNull)                        \
  V(RttCanon)                             \
  V(WasmTypeCheck)                        \
  V(WasmTypeCast)                         \
  V(AnyConvertExtern)                     \
  V(ExternConvertAny)                     \
  V(WasmTypeAnnotation)                   \
  V(StructGet)                            \
  V(StructSet)                            \
  V(StructAtomicRMW)                      \
  V(ArrayGet)                             \
  V(ArraySet)                             \
  V(ArrayAtomicRMW)                       \
  V(ArrayLength)                          \
  V(WasmAllocateArray)                    \
  V(WasmAllocateStruct)                   \
  V(WasmRefFunc)                          \
  V(StringAsWtf16)                        \
  V(StringPrepareForGetCodeUnit)          \
  V(ProcessWasmArgument)
#else
#define TURBOSHAFT_WASM_OPERATION_LIST(V)
#endif  // V8_ENABLE_WEBASSEMBLY

#ifdef V8_ENABLE_SIMD128
#define TURBOSHAFT_DEINTERLEAVED_OPERATION_LIST(V) \
  V(Simd128LoadPairDeinterleave)

#ifdef V8_ENABLE_SIMD256
#define TURBOSHAFT_SIMD256_COMMON_OPERATION_LIST(V) \
  V(Simd256Constant)                                \
  V(Simd256Extract128Lane)                          \
  V(Simd256LoadTransform)                           \
  V(Simd256Unary)                                   \
  V(Simd256Binop)                                   \
  V(Simd256Shift)                                   \
  V(Simd256Ternary)                                 \
  V(Simd256Splat)                                   \
  V(SimdPack128To256)

#if V8_TARGET_ARCH_X64
#define TURBOSHAFT_SIMD256_X64_OPERATION_LIST(V) \
  V(Simd256Shufd)                                \
  V(Simd256Shufps)                               \
  V(Simd256Unpack)

#define TURBOSHAFT_SIMD256_OPERATION_LIST(V)  \
  TURBOSHAFT_SIMD256_COMMON_OPERATION_LIST(V) \
  TURBOSHAFT_SIMD256_X64_OPERATION_LIST(V)
#else
#define TURBOSHAFT_SIMD256_OPERATION_LIST(V) \
  TURBOSHAFT_SIMD256_COMMON_OPERATION_LIST(V)
#endif  // V8_TARGET_ARCH_X64

#else
#define TURBOSHAFT_SIMD256_OPERATION_LIST(V)
#endif  // V8_ENABLE_SIMD256

#define TURBOSHAFT_SIMD_OPERATION_LIST(V) \
  V(Simd128Constant)                      \
  V(Simd128Binop)                         \
  V(Simd128Unary)                         \
  V(Simd128Reduce)                        \
  V(Simd128Shift)                         \
  V(Simd128Test)                          \
  V(Simd128Splat)                         \
  V(Simd128Ternary)                       \
  V(Simd128ExtractLane)                   \
  V(Simd128ReplaceLane)                   \
  V(Simd128MoveLane)                      \
  V(Simd128LaneMemory)                    \
  V(Simd128LoadTransform)                 \
  V(Simd128Shuffle)                       \
  TURBOSHAFT_SIMD256_OPERATION_LIST(V)    \
  TURBOSHAFT_DEINTERLEAVED_OPERATION_LIST(V)
#else
#define TURBOSHAFT_SIMD_OPERATION_LIST(V)
#endif  // V8_ENABLE_SIMD128

#define TURBOSHAFT_OPERATION_LIST_BLOCK_TERMINATOR(V) \
  V(CheckException)                                   \
  V(Goto)                                             \
  V(TailCall)                                         \
  V(Unreachable)                                      \
  V(Return)                                           \
  V(Branch)                                           \
  V(Switch)                                           \
  V(Deoptimize)                                       \
  IF_WASM(V, WasmTrap)

#ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
#define TURBOSHAFT_CPED_OPERATION_LIST(V) \
  V(GetContinuationPreservedEmbedderData) \
  V(SetContinuationPreservedEmbedderData)
#else
#define TURBOSHAFT_CPED_OPERATION_LIST(V)
#endif  // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA

// These operations should be lowered to Machine operations during
// MachineLoweringPhase.
#define TURBOSHAFT_SIMPLIFIED_OPERATION_LIST(V) \
  TURBOSHAFT_CPED_OPERATION_LIST(V)             \
  V(ArgumentsLength)                            \
  V(BigIntBinop)                                \
  V(BigIntComparison)                           \
  V(BigIntUnary)                                \
  V(CheckedClosure)                             \
  V(WordBinopDeoptOnOverflow)                   \
  V(CheckEqualsInternalizedString)              \
  V(CheckHomomorphic)                           \
  V(CheckMaps)                                  \
  V(CompareMaps)                                \
  V(FloatIs)                                    \
  V(ObjectIs)                                   \
  V(ObjectIsNumericValue)                       \
  V(Float64SameValue)                           \
  V(SameValue)                                  \
  V(ChangeOrDeopt)                              \
  V(Convert)                                    \
  V(ConvertJSPrimitiveToObject)                 \
  V(ConvertJSPrimitiveToUntagged)               \
  V(ConvertJSPrimitiveToUntaggedOrDeopt)        \
  V(ConvertUntaggedToJSPrimitive)               \
  V(ConvertWordToSmiOrDeopt)                    \
  V(TruncateJSPrimitiveToUntagged)              \
  V(TruncateJSPrimitiveToWord32OrDeopt)         \
  V(DoubleArrayMinMax)                          \
  V(EnsureWritableFastElements)                 \
  V(FastApiCall)                                \
  V(FindOrderedHashEntry)                       \
  V(WeakCollectionGet)                          \
  V(LoadDataViewElement)                        \
  V(LoadFieldByIndex)                           \
  V(LoadMessage)                                \
  V(LoadStackArgument)                          \
  V(LoadTypedElement)                           \
  V(StoreDataViewElement)                       \
  V(StoreMessage)                               \
  V(StoreTypedElement)                          \
  V(MaybeGrowFastElements)                      \
  V(NewArgumentsElements)                       \
  V(NewArray)                                   \
  V(RuntimeAbort)                               \
  V(StaticAssert)                               \
  V(StringAt)                                   \
  V(StringComparison)                           \
  V(StringConcat)                               \
  V(StringFromCodePointAt)                      \
  V(StringIndexOf)                              \
  V(StringLength)                               \
  V(StringOrOddballStrictEqual)                 \
  V(TypedArrayLength)                           \
  V(StringSubstring)                            \
  V(StringSlice)                                \
  V(NewConsString)                              \
  V(TransitionAndStoreArrayElement)             \
  V(TransitionElementsKind)                     \
  V(TransitionElementsKindOrCheckMap)           \
  V(DebugPrint)                                 \
  V(CheckTurboshaftTypeOf)                      \
  V(CheckMaglevType)                            \
  V(PrepareForLoop)

// These Operations are the lowest level handled by Turboshaft, and are
// supported by the InstructionSelector.
#define TURBOSHAFT_MACHINE_OPERATION_LIST(V) \
  V(WordBinop)                               \
  V(FloatBinop)                              \
  V(Word32PairBinop)                         \
  V(Word64AddSub128Binop)                    \
  V(Word64MulWide)                           \
  V(OverflowCheckedBinop)                    \
  V(WordUnary)                               \
  V(OverflowCheckedUnary)                    \
  V(FloatUnary)                              \
  V(Shift)                                   \
  V(Comparison)                              \
  V(Change)                                  \
  V(TryChange)                               \
  V(BitcastWord32PairToFloat64)              \
  V(TaggedBitcast)                           \
  V(Select)                                  \
  V(PendingLoopPhi)                          \
  V(Constant)                                \
  V(LoadRootRegister)                        \
  V(Load)                                    \
  IF_SANDBOX(V, LoadTrustedPointer)          \
  V(Store)                                   \
  V(Retain)                                  \
  V(Parameter)                               \
  V(OsrValue)                                \
  V(StackPointerGreaterThan)                 \
  V(StackSlot)                               \
  V(FrameConstant)                           \
  V(DeoptimizeIf)                            \
  IF_WASM(V, TrapIf)                         \
  IF_WASM(V, LoadStackPointer)               \
  IF_WASM(V, SetStackPointer)                \
  IF_WASM(V, MemoryCopy)                     \
  IF_WASM(V, MemoryFill)                     \
  IF_WASM(V, WasmFXArgBuffer)                \
  V(Phi)                                     \
  V(FrameState)                              \
  V(Call)                                    \
  V(CatchBlockBegin)                         \
  V(DidntThrow)                              \
  V(MakeTuple)                               \
  V(Projection)                              \
  V(DebugBreak)                              \
  V(AssumeMap)                               \
  V(AtomicRMW)                               \
  V(AtomicWord32Pair)                        \
  V(MemoryBarrier)                           \
  V(Comment)                                 \
  V(Dead)                                    \
  V(AbortCSADcheck)                          \
  V(Pause)                                   \
  IF_HARDWARE_SANDBOX(V, SwitchSandboxMode)

// TODO(dmercadier): StringToCaseIntl is not a JS but rather a Simplified
// operation. Consider creating a SIMPLIFIED_THROWING_OP_LIST for it, or
// lowering it already during graph building.
#define TURBOSHAFT_JS_THROWING_OPERATION_LIST(V) \
  V(GenericBinop)                                \
  V(GenericUnop)                                 \
  IF_INTL(V, StringLocaleCompareIntl)            \
  IF_INTL(V, StringToCaseIntl)                   \
  V(ToNumberOrNumeric)                           \
  V(LoadDictionaryField)

#define TURBOSHAFT_JS_OPERATION_LIST(V)        \
  TURBOSHAFT_JS_THROWING_OPERATION_LIST(V)

// These are operations that are not Machine operations and need to be lowered
// before Instruction Selection, but they are not lowered during the
// MachineLoweringPhase.
#define TURBOSHAFT_OTHER_OPERATION_LIST(V) \
  V(Allocate)                              \
  V(MajorGCForCompilerTesting)             \
  V(JSStackCheck)                          \
  IF_SANDBOX(V, LoadExternalPointer)

#define TURBOSHAFT_OPERATION_LIST_NOT_BLOCK_TERMINATOR(V) \
  TURBOSHAFT_WASM_OPERATION_LIST(V)                       \
  TURBOSHAFT_SIMD_OPERATION_LIST(V)                       \
  TURBOSHAFT_MACHINE_OPERATION_LIST(V)                    \
  TURBOSHAFT_SIMPLIFIED_OPERATION_LIST(V)                 \
  TURBOSHAFT_JS_OPERATION_LIST(V)                         \
  TURBOSHAFT_OTHER_OPERATION_LIST(V)

#define TURBOSHAFT_OPERATION_LIST(V)            \
  TURBOSHAFT_OPERATION_LIST_BLOCK_TERMINATOR(V) \
  TURBOSHAFT_OPERATION_LIST_NOT_BLOCK_TERMINATOR(V)

enum class Opcode : uint8_t {
#define ENUM_CONSTANT(Name) k##Name,
  TURBOSHAFT_OPERATION_LIST(ENUM_CONSTANT)
#undef ENUM_CONSTANT
};

const char* OpcodeName(Opcode opcode);
constexpr std::underlying_type_t<Opcode> OpcodeIndex(Opcode x) {
  return static_cast<std::underlying_type_t<Opcode>>(x);
}

#define FORWARD_DECLARE(Name) struct Name##Op;
TURBOSHAFT_OPERATION_LIST(FORWARD_DECLARE)
#undef FORWARD_DECLARE

namespace detail {
template <class Op>
struct operation_to_opcode_map {};

#define OPERATION_OPCODE_MAP_CASE(Name)    \
  template <>                              \
  struct operation_to_opcode_map<Name##Op> \
      : std::integral_constant<Opcode, Opcode::k##Name> {};
TURBOSHAFT_OPERATION_LIST(OPERATION_OPCODE_MAP_CASE)
#undef OPERATION_OPCODE_MAP_CASE
}  // namespace detail

template <typename Op>
struct operation_to_opcode
    : detail::operation_to_opcode_map<std::remove_cvref_t<Op>> {};
template <typename Op>
constexpr Opcode operation_to_opcode_v = operation_to_opcode<Op>::value;

template <typename Op, uint64_t Mask, uint64_t Value>
struct OpMaskT {
  using operation = Op;
  static constexpr uint64_t mask = Mask;
  static constexpr uint64_t value = Value;
};

#define COUNT_OPCODES(Name) +1
constexpr uint16_t kNumberOfBlockTerminatorOpcodes =
    0 TURBOSHAFT_OPERATION_LIST_BLOCK_TERMINATOR(COUNT_OPCODES);
#undef COUNT_OPCODES

#define COUNT_OPCODES(Name) +1
constexpr uint16_t kNumberOfOpcodes =
    0 TURBOSHAFT_OPERATION_LIST(COUNT_OPCODES);
#undef COUNT_OPCODES

inline constexpr bool IsBlockTerminator(Opcode opcode) {
  return OpcodeIndex(opcode) < kNumberOfBlockTerminatorOpcodes;
}

// Operations that can throw and that have static output representations.
#define TURBOSHAFT_THROWING_STATIC_OUTPUTS_OPERATIONS_LIST(V) \
  TURBOSHAFT_JS_THROWING_OPERATION_LIST(V)

// This list repeats the operations that may throw and need to be followed by
// `DidntThrow`.
#define TURBOSHAFT_THROWING_OPERATIONS_LIST(V)          \
  TURBOSHAFT_THROWING_STATIC_OUTPUTS_OPERATIONS_LIST(V) \
  V(Call)                                               \
  V(FastApiCall)

// Operations that need to be followed by `DidntThrowOp`.
inline constexpr bool MayThrow(Opcode opcode) {
#define CASE(Name) case Opcode::k##Name:
  switch (opcode) {
    TURBOSHAFT_THROWING_OPERATIONS_LIST(CASE)
    return true;
    default:
      return false;
  }
#undef CASE
}

// For Throwing operations, outputs_rep() are empty, because the values are
// produced by the subsequent DidntThrow. Nevertheless, the operation has to
// define its output representations in an array that DidntThrow can then reuse
// to know what its outputs are. Additionally, when using Maglev as a frontend,
// catch handlers that have never been reach so far are not emitted, and instead
// the throwing operations lazy deopt instead of throwing.
//
// That's where the THROWING_OP_BOILERPLATE macro comes in: it creates  an array
// of representations that DidntThrow can use, and will define outputs_rep() to
// be empty, and takes care of creating a LazyDeoptOnThrow member. For instance:
//
//    THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged(),
//                            RegisterRepresentation::Word32())
//
// Warning: don't forget to add `lazy_deopt_on_throw` to the `options` of your
// Operation (you'll get a compile-time error if you forget it).
#define THROWING_OP_BOILERPLATE(...)                                         \
  static constexpr RegisterRepresentation kOutputRepsStorage[]{__VA_ARGS__}; \
  static constexpr base::Vector<const RegisterRepresentation> kOutReps =     \
      base::VectorOf(kOutputRepsStorage, arraysize(kOutputRepsStorage));     \
  base::Vector<const RegisterRepresentation> outputs_rep() const {           \
    return {};                                                               \
  }                                                                          \
  LazyDeoptOnThrow lazy_deopt_on_throw;

template <typename T>
inline base::Vector<T> InitVectorOf(
    ZoneVector<T>& storage,
    std::initializer_list<RegisterRepresentation> values) {
  storage.resize(values.size());
  size_t i = 0;
  for (auto&& value : values) {
    storage[i++] = value;
  }
  return base::VectorOf(storage);
}

class InputsRepFactory {
 public:
  constexpr static base::Vector<const MaybeRegisterRepresentation> SingleRep(
      RegisterRepresentation rep) {
    return base::VectorOf(ToMaybeRepPointer(rep), 1);
  }

  constexpr static base::Vector<const MaybeRegisterRepresentation> PairOf(
      RegisterRepresentation rep) {
    return base::VectorOf(ToMaybeRepPointer(rep), 2);
  }

 protected:
  constexpr static const MaybeRegisterRepresentation* ToMaybeRepPointer(
      RegisterRepresentation rep) {
    size_t index = static_cast<size_t>(rep.value()) * 2;
    DCHECK_LT(index, arraysize(rep_map));
    return &rep_map[index];
  }

 private:
  constexpr static MaybeRegisterRepresentation rep_map[] = {
      MaybeRegisterRepresentation::Word32(),
      MaybeRegisterRepresentation::Word32(),
      MaybeRegisterRepresentation::Word64(),
      MaybeRegisterRepresentation::Word64(),
      MaybeRegisterRepresentation::Float32(),
      MaybeRegisterRepresentation::Float32(),
      MaybeRegisterRepresentation::Float64(),
      MaybeRegisterRepresentation::Float64(),
      MaybeRegisterRepresentation::Tagged(),
      MaybeRegisterRepresentation::Tagged(),
      MaybeRegisterRepresentation::Compressed(),
      MaybeRegisterRepresentation::Compressed(),
      MaybeRegisterRepresentation::Simd128(),
      MaybeRegisterRepresentation::Simd128(),
#ifdef V8_ENABLE_SIMD256
      MaybeRegisterRepresentation::Simd256(),
      MaybeRegisterRepresentation::Simd256(),
#endif  // V8_ENABLE_SIMD256
  };
};

struct __attribute__((packed)) EffectDimensions {
  // Produced by loads, consumed by operations that should not move before loads
  // because they change memory.
  bool load_heap_memory : 1;
  bool load_off_heap_memory : 1;

  // Produced by stores, consumed by operations that should not move before
  // stores because they load or store memory.
  bool store_heap_memory : 1;
  bool store_off_heap_memory : 1;

  // Operations that perform raw heap access (like initialization) consume
  // `before_raw_heap_access` and produce `after_raw_heap_access`.
  // Operations that need the heap to be in a consistent state produce
  // `before_raw_heap_access` and consume `after_raw_heap_access`.
  bool before_raw_heap_access : 1;
  // Produced by operations that access raw/untagged pointers into the
  // heap or keep such a pointer alive, consumed by operations that can GC to
  // ensure they don't move before the raw access.
  bool after_raw_heap_access : 1;

  // Produced by any operation that can affect whether subsequent operations are
  // executed, for example by branching, deopting, throwing or aborting.
  // Consumed by all operations that should not be hoisted before a check
  // because they rely on it. For example, loads usually rely on the shape of
  // the heap object or the index being in bounds.
  bool control_flow : 1;
  // We need to ensure that the padding bits have a specified value, as they are
  // observable in bitwise operations.
  uint8_t unused_padding : 1;

  using Bits = uint8_t;
  constexpr EffectDimensions()
      : load_heap_memory(false),
        load_off_heap_memory(false),
        store_heap_memory(false),
        store_off_heap_memory(false),
        before_raw_heap_access(false),
        after_raw_heap_access(false),
        control_flow(false),
        unused_padding(0) {}
  Bits bits() const { return base::bit_cast<Bits>(*this); }
  static EffectDimensions FromBits(Bits bits) {
    return base::bit_cast<EffectDimensions>(bits);
  }
  bool operator==(EffectDimensions other) const {
    return bits() == other.bits();
  }
  bool operator!=(EffectDimensions other) const {
    return bits() != other.bits();
  }
};
static_assert(sizeof(EffectDimensions) == sizeof(EffectDimensions::Bits));

// Possible reorderings are restricted using two bit vectors: `produces` and
// `consumes`. Two operations cannot be reordered if the first operation
// produces an effect dimension that the second operation consumes. This is not
// necessarily symmetric. For example, it is possible to reorder
//     Load(x)
//     CheckMaps(y)
// to become
//     CheckMaps(x)
//     Load(y)
// because the load cannot affect the map check. But the other direction could
// be unsound, if the load depends on the map check having been executed. The
// former reordering is useful to push a load across a check into a branch if
// it is only needed there. The effect system expresses this by having the map
// check produce `EffectDimensions::control_flow` and the load consuming
// `EffectDimensions::control_flow`. If the producing operation comes before the
// consuming operation, then this order has to be preserved. But if the
// consuming operation comes first, then we are free to reorder them. Operations
// that produce and consume the same effect dimension always have a fixed order
// among themselves. For example, stores produce and consume the store
// dimensions. It is possible for operations to be reorderable unless certain
// other operations appear in-between. This way, the IR can be generous with
// reorderings as long as all operations are high-level, but become more
// restrictive as soon as low-level operations appear. For example, allocations
// can be freely reordered. Tagged bitcasts can be reordered with other tagged
// bitcasts. But a tagged bitcast cannot be reordered with allocations, as this
// would mean that an untagged pointer can be alive while a GC is happening. The
// way this works is that allocations produce the `before_raw_heap_access`
// dimension and consume the `after_raw_heap_access` dimension to stay either
// before or after a raw heap access. This means that there are no ordering
// constraints between allocations themselves. Bitcasts should not
// be moved accross an allocation. We treat them as raw heap access by letting
// them consume `before_raw_heap_access` and produce `after_raw_heap_access`.
// This way, allocations cannot be moved across bitcasts. Similarily,
// initializing stores and uninitialized allocations are classified as raw heap
// access, to prevent any operation that relies on a consistent heap state to be
// scheduled in the middle of an inline allocation. As long as we didn't lower
// to raw heap accesses yet, pure allocating operations or operations reading
// immutable memory can float freely. As soon as there are raw heap accesses,
// they become more restricted in their movement. Note that calls are not the
// most side-effectful operations, as they do not leave the heap in an
// inconsistent state, so they do not need to be marked as raw heap access.
struct __attribute__((packed)) OpEffects {
  EffectDimensions produces;
  EffectDimensions consumes;

  // Operations that cannot be merged because they produce identity.  That is,
  // every repetition can produce a different result, but the order in which
  // they are executed does not matter. All we care about is that they are
  // different. Producing a random number or allocating an object with
  // observable pointer equality are examples. Producing identity doesn't
  // restrict reordering in straight-line code, but we must prevent using GVN or
  // moving identity-producing operations in- or out of loops.
  bool can_create_identity : 1;
  // If the operation can allocate and therefore can trigger GC.
  bool can_allocate : 1;
  // Instructions that have no uses but are `required_when_unused` should not be
  // removed.
  bool required_when_unused : 1;
  // We need to ensure that the padding bits have a specified value, as they are
  // observable in bitwise operations.  This is split into two fields so that
  // also MSVC creates the correct object layout.
  uint8_t unused_padding_1 : 5;
  uint8_t unused_padding_2;

  constexpr OpEffects()
      : can_create_identity(false),
        can_allocate(false),
        required_when_unused(false),
        unused_padding_1(0),
        unused_padding_2(0) {}

  using Bits = uint32_t;
  Bits bits() const { return base::bit_cast<Bits>(*this); }
  static OpEffects FromBits(Bits bits) {
    return base::bit_cast<OpEffects>(bits);
  }

  bool operator==(OpEffects other) const { return bits() == other.bits(); }
  bool operator!=(OpEffects other) const { return bits() != other.bits(); }
  OpEffects operator|(OpEffects other) const {
    return FromBits(bits() | other.bits());
  }
  OpEffects operator&(OpEffects other) const {
    return FromBits(bits() & other.bits());
  }
  bool IsSubsetOf(OpEffects other) const {
    return (bits() & ~other.bits()) == 0;
  }

  constexpr OpEffects AssumesConsistentHeap() const {
    OpEffects result = *this;
    // Do not move the operation into a region with raw heap access.
    result.produces.before_raw_heap_access = true;
    result.consumes.after_raw_heap_access = true;
    return result;
  }
  // Like `CanAllocate()`, but allocated values must be immutable and not have
  // identity (for example `HeapNumber`).
  // Note that if we first allocate something as mutable and later make it
  // immutable, we have to allocate it with identity.
  constexpr OpEffects CanAllocateWithoutIdentity() const {
    OpEffects result = AssumesConsistentHeap();
    result.can_allocate = true;
    return result;
  }
  // Allocations change the GC state and can trigger GC, as well as produce a
  // fresh identity.
  constexpr OpEffects CanAllocate() const {
    return CanAllocateWithoutIdentity().CanCreateIdentity();
  }
  // The operation can leave the heap in an incosistent state or have untagged
  // pointers into the heap as input or output.
  constexpr OpEffects CanDoRawHeapAccess() const {
    OpEffects result = *this;
    // Do not move any operation that relies on a consistent heap state accross.
    result.produces.after_raw_heap_access = true;
    result.consumes.before_raw_heap_access = true;
    return result;
  }
  // Reading mutable heap memory. Reading immutable memory doesn't count.
  constexpr OpEffects CanReadHeapMemory() const {
    OpEffects result = *this;
    result.produces.load_heap_memory = true;
    // Do not reorder before stores.
    result.consumes.store_heap_memory = true;
    return result;
  }
  // Reading mutable off-heap memory or other input. Reading immutable memory
  // doesn't count.
  constexpr OpEffects CanReadOffHeapMemory() const {
    OpEffects result = *this;
    result.produces.load_off_heap_memory = true;
    // Do not reorder before stores.
    result.consumes.store_off_heap_memory = true;
    return result;
  }
  // Writing any off-memory or other output.
  constexpr OpEffects CanWriteOffHeapMemory() const {
    OpEffects result = *this;
    result.required_when_unused = true;
    result.produces.store_off_heap_memory = true;
    // Do not reorder before stores.
    result.consumes.store_off_heap_memory = true;
    // Do not reorder before loads.
    result.consumes.load_off_heap_memory = true;
    // Do not move before deopting or aborting operations.
    result.consumes.control_flow = true;
    return result;
  }
  // Writing heap memory that existed before the operation started. Initializing
  // newly allocated memory doesn't count.
  constexpr OpEffects CanWriteHeapMemory() const {
    OpEffects result = *this;
    result.required_when_unused = true;
    result.produces.store_heap_memory = true;
    // Do not reorder before stores.
    result.consumes.store_heap_memory = true;
    // Do not reorder before loads.
    result.consumes.load_heap_memory = true;
    // Do not move before deopting or aborting operations.
    result.consumes.control_flow = true;
    return result;
  }
  // Writing any memory or other output, on- or off-heap.
  constexpr OpEffects CanWriteMemory() const {
    return CanWriteHeapMemory().CanWriteOffHeapMemory();
  }
  // Reading any memory or other input, on- or off-heap.
  constexpr OpEffects CanReadMemory() const {
    return CanReadHeapMemory().CanReadOffHeapMemory();
  }
  // The operation might read immutable data from the heap, so it can be freely
  // reordered with operations that keep the heap in a consistent state. But we
  // must prevent the operation from observing an incompletely initialized
  // object.
  constexpr OpEffects CanReadImmutableMemory() const {
    OpEffects result = AssumesConsistentHeap();
    return result;
  }
  // Partial operations that are only safe to execute after we performed certain
  // checks, for example loads may only be safe after a corresponding bound or
  // map checks.
  constexpr OpEffects CanDependOnChecks() const {
    OpEffects result = *this;
    result.consumes.control_flow = true;
    return result;
  }
  // The operation can affect control flow (like branch, deopt, throw or crash).
  constexpr OpEffects CanChangeControlFlow() const {
    OpEffects result = *this;
    result.required_when_unused = true;
    // Signal that this changes control flow. Prevents stores or operations
    // relying on checks from flowing before this operation.
    result.produces.control_flow = true;
    // Stores must not flow past something that affects control flow.
    result.consumes.store_heap_memory = true;
    result.consumes.store_off_heap_memory = true;
    return result;
  }
  // Execution of the current function may end with this operation, for example
  // because of return, deopt, exception throw or abort/trap.
  constexpr OpEffects CanLeaveCurrentFunction() const {
    // All memory becomes observable.
    return AssumesConsistentHeap()
        .CanChangeControlFlow()
        .CanReadMemory()
        .RequiredWhenUnused();
  }
  // The operation can deopt.
  constexpr OpEffects CanDeopt() const {
    return CanLeaveCurrentFunction()
        // We might depend on previous checks to avoid deopting.
        .CanDependOnChecks();
  }
  constexpr OpEffects CanThrowOrTrap() const {
    // Allocates an exception.
    return CanLeaveCurrentFunction().CanAllocate();
  }
  // Producing identity doesn't prevent reorderings, but it prevents GVN from
  // de-duplicating identical operations.
  constexpr OpEffects CanCreateIdentity() const {
    OpEffects result = *this;
    result.can_create_identity = true;
    return result;
  }
  // The set of all possible effects.
  constexpr OpEffects CanCallAnything() const {
    return CanReadMemory()
        .CanWriteMemory()
        .CanAllocate()
        .CanChangeControlFlow()
        .CanDependOnChecks()
        .RequiredWhenUnused();
  }
  constexpr OpEffects RequiredWhenUnused() const {
    OpEffects result = *this;
    result.required_when_unused = true;
    return result;
  }

  // Operations that can be removed if their result is not used. Unused
  // allocations can be removed.
  constexpr bool is_required_when_unused() const {
    return required_when_unused;
  }
  // Operations that can be moved before a preceding branch or check.
  bool hoistable_before_a_branch() const {
    // Since this excludes `CanDependOnChecks()`, most loads actually cannot be
    // hoisted.
    return IsSubsetOf(OpEffects().CanReadMemory());
  }
  bool can_read_mutable_memory() const {
    return produces.load_heap_memory | produces.load_off_heap_memory;
  }
  bool requires_consistent_heap() const {
    return produces.before_raw_heap_access | consumes.after_raw_heap_access;
  }
  bool can_write() const {
    return produces.store_heap_memory | produces.store_off_heap_memory;
  }
  bool can_be_constant_folded() const {
    // Operations that CanDependOnChecks can still be constant-folded. If they
    // did indeed depend on a check, then their result will only be used after
    // said check has been executed anyways.
    return IsSubsetOf(OpEffects().CanDependOnChecks());
  }
};
static_assert(sizeof(OpEffects) == sizeof(OpEffects::Bits));

V8_INLINE size_t hash_value(OpEffects effects) {
  return static_cast<size_t>(effects.bits());
}

inline bool CannotSwapOperations(OpEffects first, OpEffects second) {
  return first.produces.bits() & (second.consumes.bits());
}

inline bool CannotSwapTrappingLoads(OpEffects first, OpEffects second) {
  EffectDimensions produces = first.produces;
  // The control flow effects produced by Loads are due to trap handler. We can
  // ignore this kind of effect when swapping two Loads that both have trap
  // handler.
  produces.control_flow = false;
  return produces.bits() & (second.consumes.bits());
}

V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           OpEffects op_effects);

// SaturatedUint8 is a wrapper around a uint8_t, which can be incremented and
// decremented with the `Incr` and `Decr` methods. These methods prevent over-
// and underflow, and saturate once the uint8_t reaches the maximum (255):
// future increment and decrement will not change the value then.
// We purposefuly do not expose the uint8_t directly, so that users go through
// Incr/Decr/SetToZero/SetToOne to manipulate it, so that the saturation and
// lack of over/underflow is always respected.
class SaturatedUint8 {
 public:
  SaturatedUint8() = default;

  void Incr() {
    if (V8_LIKELY(val != kMax)) {
      val++;
    }
  }
  void Decr() {
    if (V8_LIKELY(val != 0 && val != kMax)) {
      val--;
    }
  }

  void SetToZero() { val = 0; }
  void SetToOne() { val = 1; }

  bool Is(int v) const {
    if (IsSaturated()) return false;
    return val == v;
  }

  bool IsSaturated() const { return val == kMax; }

  uint8_t GetUnsaturated() const {
    CHECK(!IsSaturated());
    return val;
  }

  uint8_t GetMaybeSaturated() const { return val; }

  SaturatedUint8& operator+=(const SaturatedUint8& other) {
    uint32_t sum = val;
    sum += other.val;
    val = static_cast<uint8_t>(std::min<uint32_t>(sum, kMax));
    return *this;
  }

  static SaturatedUint8 FromSize(size_t value) {
    uint8_t val = static_cast<uint8_t>(std::min<size_t>(value, kMax));
    return SaturatedUint8{val};
  }

 private:
  explicit SaturatedUint8(uint8_t val) : val(val) {}
  uint8_t val = 0;
#ifdef V8_LOWER_LIMITS_MODE
  static constexpr uint8_t kMax = 5;
#else
  static constexpr uint8_t kMax = std::numeric_limits<uint8_t>::max();
#endif
};

// underlying_operation<> is used to extract the operation type from OpMaskT
// classes used in Operation::Is<> and Operation::TryCast<>.
template <typename T>
struct underlying_operation {
  using type = T;
};
template <typename T, uint64_t M, uint64_t V>
struct underlying_operation<OpMaskT<T, M, V>> {
  using type = T;
};
template <typename T>
using underlying_operation_t = typename underlying_operation<T>::type;

// Baseclass for all Turboshaft operations.
// The `alignas(OpIndex)` is necessary because it is followed by an array of
// `OpIndex` inputs.
struct alignas(OpIndex) Operation {
  struct IdentityMapper {
    OpIndex Map(OpIndex index) { return index; }
    OptionalOpIndex Map(OptionalOpIndex index) { return index; }
    template <size_t N>
    base::SmallVector<OpIndex, N> Map(base::Vector<const OpIndex> indices) {
      return base::SmallVector<OpIndex, N>{indices};
    }
  };

  const Opcode opcode;

  // The number of uses of this operation in the current graph.
  // Instead of overflowing, we saturate the value if it reaches the maximum. In
  // this case, the true number of uses is unknown.
  // We use such a small type to save memory and because nodes with a high
  // number of uses are rare. Additionally, we usually only care if the number
  // of uses is 0, 1 or bigger than 1.
  SaturatedUint8 saturated_use_count;

  const uint16_t input_count;

  // The inputs are stored adjacent in memory, right behind the `Operation`
  // object.
  base::Vector<const OpIndex> inputs() const;
  V8_INLINE OpIndex input(size_t i) const { return inputs()[i]; }

  static size_t StorageSlotCount(Opcode opcode, size_t input_count);
  size_t StorageSlotCount() const {
    return StorageSlotCount(opcode, input_count);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const;
  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const;

  template <class Op>
  bool Is() const {
    if constexpr (std::is_base_of_v<Operation, Op>) {
      return opcode == Op::opcode;
    } else {
      // Otherwise this must be OpMaskT.
      return IsOpmask<Op>();
    }
  }
  template <class Op>
  underlying_operation_t<Op>& Cast() {
    DCHECK(Is<Op>());
    return *static_cast<underlying_operation_t<Op>*>(this);
  }
  template <class Op>
  const underlying_operation_t<Op>& Cast() const {
    DCHECK(Is<Op>());
    return *static_cast<const underlying_operation_t<Op>*>(this);
  }
  template <class Op>
  const underlying_operation_t<Op>* TryCast() const {
    if (!Is<Op>()) return nullptr;
    return static_cast<const underlying_operation_t<Op>*>(this);
  }
  template <class Op>
  underlying_operation_t<Op>* TryCast() {
    if (!Is<Op>()) return nullptr;
    return static_cast<underlying_operation_t<Op>*>(this);
  }
  OpEffects Effects() const;
  bool IsBlockTerminator() const {
    return turboshaft::IsBlockTerminator(opcode);
  }
  bool IsRequiredWhenUnused() const {
    DCHECK_IMPLIES(IsBlockTerminator(), Effects().is_required_when_unused());
    return Effects().is_required_when_unused();
  }
  bool IsTrappingLoad() const;

  std::string ToString() const;
  void PrintInputs(std::ostream& os, const std::string& op_index_prefix) const;
  void PrintOptions(std::ostream& os) const;

  // Returns true if {this} is the only operation using {value}.
  bool IsOnlyUserOf(const Operation& value, const Graph& graph) const;

  void Print() const;

 protected:
  // Operation objects store their inputs behind the object. Therefore, they can
  // only be constructed as part of a Graph.
  explicit Operation(Opcode opcode, size_t input_count)
      : opcode(opcode), input_count(input_count) {
    DCHECK_LE(input_count,
              std::numeric_limits<decltype(this->input_count)>::max());
  }

  template <class OpmaskT>
  // A Turboshaft operation can be as small as 4 Bytes while Opmasks can span up
  // to 8 Bytes. Any mask larger than the operation it is compared with will
  // always have a mismatch in the initialized memory. Still, there can be some
  // uninitialized memory being compared as part of the 8 Byte comparison that
  // this function performs.
  V8_CLANG_NO_SANITIZE("memory") bool IsOpmask() const {
    static_assert(std::is_same_v<
                  underlying_operation_t<OpmaskT>,
                  typename OpMaskT<typename OpmaskT::operation, OpmaskT::mask,
                                   OpmaskT::value>::operation>);
    // We check with the given mask.
    uint64_t b;
    memcpy(&b, this, sizeof(uint64_t));
    b &= OpmaskT::mask;
    return b == OpmaskT::value;
  }

  Operation(const Operation&) = delete;
  Operation& operator=(const Operation&) = delete;
};

struct OperationPrintStyle {
  const Operation& op;
  const char* op_index_prefix = "#";
};

V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           OperationPrintStyle op);
inline std::ostream& operator<<(std::ostream& os, const Operation& op) {
  return os << OperationPrintStyle{op};
}
std::ostream& operator<<(std::ostream& os, RootIndex index);

V8_EXPORT_PRIVATE Zone* get_zone(Graph* graph);

OperationStorageSlot* AllocateOpStorage(Graph* graph, size_t slot_count);
V8_EXPORT_PRIVATE const Operation& Get(const Graph& graph, OpIndex index);

// Determine if an operation declares `effects`, which means that its
// effects are static and don't depend on inputs or options.
template <class Op, class = void>
struct HasStaticEffects : std::bool_constant<false> {};
template <class Op>
struct HasStaticEffects<Op, std::void_t<decltype(Op::effects)>>
    : std::bool_constant<true> {};

// This template knows the complete type of the operation and is plugged into
// the inheritance hierarchy. It removes boilerplate from the concrete
// `Operation` subclasses, defining everything that can be expressed
// generically. It overshadows many methods from `Operation` with ones that
// exploit additional static information.
template <class Derived>
struct OperationT : Operation {
  // Enable concise base-constructor call in derived struct.
  using Base = OperationT;

  static const Opcode opcode;

  static constexpr OpEffects Effects() { return Derived::effects; }
  static constexpr bool IsBlockTerminator() {
    return turboshaft::IsBlockTerminator(opcode);
  }
  bool IsRequiredWhenUnused() const {
    return IsBlockTerminator() ||
           derived_this().Effects().is_required_when_unused();
  }

  static constexpr std::optional<OpEffects> EffectsIfStatic() {
    if constexpr (HasStaticEffects<Derived>::value) {
      return Derived::Effects();
    }
    return std::nullopt;
  }

  Derived& derived_this() { return *static_cast<Derived*>(this); }
  const Derived& derived_this() const {
    return *static_cast<const Derived*>(this);
  }

  // Shadow Operation::inputs to exploit static knowledge about object size.
  base::Vector<OpIndex> inputs() {
    return {reinterpret_cast<OpIndex*>(reinterpret_cast<char*>(this) +
                                       sizeof(Derived)),
            derived_this().input_count};
  }
  base::Vector<const OpIndex> inputs() const {
    return {reinterpret_cast<const OpIndex*>(
                reinterpret_cast<const char*>(this) + sizeof(Derived)),
            derived_this().input_count};
  }

  V8_INLINE OpIndex& input(size_t i) { return derived_this().inputs()[i]; }
  // TODO(chromium:331100916): remove this V<Any> overload once all users use
  // the more specific V<T> overload.
  V8_INLINE V<Any> input(size_t i) const { return derived_this().inputs()[i]; }
  template <typename T>
  V8_INLINE V<T> input(size_t i) const {
    static_assert(!std::is_same_v<T, AnyFrameState>,
                  "Operations should not have AnyFrameState inputs! Use "
                  "EagerFrameState or LazyFrameState instead.");
    return V<T>::Cast(derived_this().inputs()[i]);
  }

  static size_t StorageSlotCount(size_t input_count) {
    // The operation size in bytes is:
    //   `sizeof(Derived) + input_count*sizeof(OpIndex)`.
    // This is an optimized computation of:
    //   round_up(size_in_bytes / sizeof(StorageSlot))
    constexpr size_t r = sizeof(OperationStorageSlot) / sizeof(OpIndex);
    static_assert(sizeof(OperationStorageSlot) % sizeof(OpIndex) == 0);
    static_assert(sizeof(Derived) % sizeof(OpIndex) == 0);
    size_t result = std::max<size_t>(
        2, (r - 1 + sizeof(Derived) / sizeof(OpIndex) + input_count) / r);
    DCHECK_EQ(result, Operation::StorageSlotCount(opcode, input_count));
    return result;
  }
  size_t StorageSlotCount() const { return StorageSlotCount(input_count); }

  template <class... Args>
  static Derived& New(Graph* graph, size_t input_count, Args... args) {
    OperationStorageSlot* ptr =
        AllocateOpStorage(graph, StorageSlotCount(input_count));
    Derived* result = new (ptr) Derived(args...);
#ifdef DEBUG
    result->Validate(*graph);
    ZoneVector<MaybeRegisterRepresentation> storage(get_zone(graph));
    base::Vector<const MaybeRegisterRepresentation> expected =
        result->inputs_rep(storage);
    // TODO(mliedtke): DCHECK that expected and inputs are of the same size
    // and adapt inputs_rep() to always emit a representation for all inputs.
    size_t end = std::min<size_t>(expected.size(), result->input_count);
    for (size_t i = 0; i < end; ++i) {
      if (expected[i] == MaybeRegisterRepresentation::None()) continue;
      ValidateOpInputRep(*graph, result->inputs()[i],
                         RegisterRepresentation(expected[i]), result);
    }
#endif
    // If this DCHECK fails, then the number of inputs specified in the
    // operation constructor and in the static New function disagree.
    DCHECK_EQ(input_count, result->Operation::input_count);
    return *result;
  }

  template <class... Args>
  static Derived& New(Graph* graph, ShadowyOpIndexVectorWrapper inputs,
                      Args... args) {
    return New(graph, inputs.size(), inputs, args...);
  }

  explicit OperationT(size_t input_count) : Operation(opcode, input_count) {
    static_assert((std::is_base_of_v<OperationT, Derived>));
#if !V8_CC_MSVC
    static_assert(std::is_trivially_copyable_v<Derived>);
#endif  // !V8_CC_MSVC
    static_assert(std::is_trivially_destructible_v<Derived>);
  }
  explicit OperationT(ShadowyOpIndexVectorWrapper inputs)
      : OperationT(inputs.size()) {
    this->inputs().OverwriteWith(
        static_cast<base::Vector<const OpIndex>>(inputs));
  }

  bool EqualsForGVN(const Base& other) const {
    // By default, GVN only removed identical Operations. However, some
    // Operations (like DeoptimizeIf) can be GVNed when a dominating
    // similar-but-not-identical one exists. In that case, the Operation should
    // redefine EqualsForGVN, so that GVN knows which inputs or options of the
    // Operation to ignore (you should also probably redefine hash_value,
    // otherwise GVN won't even try to call EqualsForGVN).
    return derived_this() == other.derived_this();
  }
  bool operator==(const Base& other) const {
    return derived_this().inputs() == other.derived_this().inputs() &&
           derived_this().options() == other.derived_this().options();
  }
  template <typename... Args>
  size_t HashWithOptions(const Args&... args) const {
    return fast_hash_combine(opcode, derived_this().inputs(), args...);
  }
  size_t hash_value(
      HashingStrategy strategy = HashingStrategy::kDefault) const {
    return HashWithOptions(derived_this().options());
  }

  void PrintInputs(std::ostream& os, const std::string& op_index_prefix) const {
    os << "(";
    bool first = true;
    for (OpIndex input : inputs()) {
      if (!first) os << ", ";
      first = false;
      os << op_index_prefix << input.id();
    }
    os << ")";
  }

  void PrintOptions(std::ostream& os) const {
    const auto& options = derived_this().options();
    constexpr size_t options_count =
        std::tuple_size<std::remove_reference_t<decltype(options)>>::value;
    if (options_count == 0) {
      return;
    }
    PrintOptionsHelper(os, options, std::make_index_sequence<options_count>());
  }

  // Check graph invariants for this operation. Will be invoked in debug mode
  // immediately upon construction.
  // Concrete Operator classes are expected to re-define it.
  void Validate(const Graph& graph) const {}

 private:
  template <class... T, size_t... I>
  static void PrintOptionsHelper(std::ostream& os,
                                 const std::tuple<T...>& options,
                                 std::index_sequence<I...>) {
    os << "[";
    bool first = true;
    USE(first);
    ((first ? (first = false, os << std::get<I>(options))
            : os << ", " << std::get<I>(options)),
     ...);
    os << "]";
  }

  // All Operations have to define the outputs_rep function, to which
  // Operation::outputs_rep() will forward, based on their opcode. If you forget
  // to define it, then Operation::outputs_rep() would forward to itself,
  // resulting in an infinite loop. To avoid this, we define here in OperationT
  // a private version outputs_rep (with no implementation): if an operation
  // forgets to define outputs_rep, then Operation::outputs_rep() tries to call
  // this private version, which fails at compile time.
  base::Vector<const RegisterRepresentation> outputs_rep() const;

  // Returns a vector of the input representations.
  // The passed in {storage} can be used to store the underlying data.
  // The returned vector might be smaller than the input_count in which case the
  // additional inputs are assumed to have no register representation.
  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const;
};

template <size_t InputCount, class Derived>
struct FixedArityOperationT : OperationT<Derived> {
  // Enable concise base access in derived struct.
  using Base = FixedArityOperationT;

  // Shadow Operation::input_count to exploit static knowledge.
  static constexpr uint16_t input_count = InputCount;

  template <class... Args>
  explicit FixedArityOperationT(Args... args)
      : OperationT<Derived>(InputCount) {
    static_assert(sizeof...(Args) == InputCount, "wrong number of inputs");
    size_t i = 0;
    OpIndex* inputs = this->inputs().begin();
    ((inputs[i++] = args), ...);
  }

  // Redefine the input initialization to tell C++ about the static input size.
  template <class... Args>
  static Derived& New(Graph* graph, Args... args) {
    Derived& result =
        OperationT<Derived>::New(graph, InputCount, std::move(args)...);
    return result;
  }

  template <typename Fn, typename Mapper, size_t... InputI, size_t... OptionI>
  V8_INLINE auto ExplodeImpl(Fn fn, Mapper& mapper,
                             std::index_sequence<InputI...>,
                             std::index_sequence<OptionI...>) const {
    auto options = this->derived_this().options();
    USE(options);
    return fn(mapper.Map(this->input(InputI))...,
              std::get<OptionI>(options)...);
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return ExplodeImpl(
        fn, mapper, std::make_index_sequence<input_count>(),
        std::make_index_sequence<
            std::tuple_size_v<decltype(this->derived_this().options())>>());
  }
};

#define SUPPORTED_OPERATIONS_LIST(V)                       \
  V(float32_round_down, Float32RoundDown)                  \
  V(float64_round_down, Float64RoundDown)                  \
  V(float32_round_up, Float32RoundUp)                      \
  V(float64_round_up, Float64RoundUp)                      \
  V(float32_round_to_zero, Float32RoundTruncate)           \
  V(float64_round_to_zero, Float64RoundTruncate)           \
  V(float32_round_ties_even, Float32RoundTiesEven)         \
  V(float64_round_ties_even, Float64RoundTiesEven)         \
  V(float64_round_ties_away, Float64RoundTiesAway)         \
  V(int32_div_is_safe, Int32DivIsSafe)                     \
  V(uint32_div_is_safe, Uint32DivIsSafe)                   \
  V(word32_shift_is_safe, Word32ShiftIsSafe)               \
  V(word32_ctz, Word32Ctz)                                 \
  V(word64_ctz, Word64Ctz)                                 \
  V(word64_ctz_lowerable, Word64CtzLowerable)              \
  V(word32_popcnt, Word32Popcnt)                           \
  V(word64_popcnt, Word64Popcnt)                           \
  V(word32_reverse_bits, Word32ReverseBits)                \
  V(word64_reverse_bits, Word64ReverseBits)                \
  V(float32_select, Float32Select)                         \
  V(float64_select, Float64Select)                         \
  V(float64_min_max, Float64MinMax)                        \
  V(int32_abs_with_overflow, Int32AbsWithOverflow)         \
  V(int64_abs_with_overflow, Int64AbsWithOverflow)         \
  V(word32_rol, Word32Rol)                                 \
  V(word64_rol, Word64Rol)                                 \
  V(word64_rol_lowerable, Word64RolLowerable)              \
  V(sat_conversion_is_safe, SatConversionIsSafe)           \
  V(word32_select, Word32Select)                           \
  V(word64_select, Word64Select)                           \
  V(float16_mem_access, Float16MemAccess)                  \
  V(float16_raw_bits_conversion, Float16RawBitsConversion) \
  V(float16_arithmetic, Float16Arithmetic)

class V8_EXPORT_PRIVATE SupportedOperations {
#define DECLARE_FIELD(name, machine_name) bool name##_;
#define DECLARE_GETTER(name, machine_name)     \
  static bool name() {                         \
    if constexpr (DEBUG_BOOL) {                \
      base::MutexGuard lock(mutex_.Pointer()); \
      DCHECK(initialized_);                    \
    }                                          \
    return instance_.name##_;                  \
  }

 public:
  static void Initialize();
  static bool IsUnalignedLoadSupported(MemoryRepresentation repr);
  static bool IsUnalignedStoreSupported(MemoryRepresentation repr);
  // Unaligned Load and Store support for all MemoryRepresentations.
  static bool HasFullUnalignedSupport();
  SUPPORTED_OPERATIONS_LIST(DECLARE_GETTER)

 private:
  SUPPORTED_OPERATIONS_LIST(DECLARE_FIELD)

  static bool initialized_;
  static base::LazyMutex mutex_;
  static SupportedOperations instance_;

#undef DECLARE_FIELD
#undef DECLARE_GETTER
};

template <RegisterRepresentation::Enum... reps>
base::Vector<const RegisterRepresentation> RepVector() {
  static constexpr std::array<RegisterRepresentation, sizeof...(reps)>
      rep_array{RegisterRepresentation{reps}...};
  return base::VectorOf(rep_array);
}

template <MaybeRegisterRepresentation::Enum... reps>
base::Vector<const MaybeRegisterRepresentation> MaybeRepVector() {
  static constexpr std::array<MaybeRegisterRepresentation, sizeof...(reps)>
      rep_array{MaybeRegisterRepresentation{reps}...};
  return base::VectorOf(rep_array);
}

#if DEBUG
V8_EXPORT_PRIVATE void ValidateOpInputRep(
    const Graph& graph, OpIndex input,
    std::initializer_list<RegisterRepresentation> expected_rep,
    const Operation* checked_op = nullptr,
    std::optional<size_t> projection_index = {});
V8_EXPORT_PRIVATE void ValidateOpInputRep(
    const Graph& graph, OpIndex input, RegisterRepresentation expected_rep,
    const Operation* checked_op = nullptr,
    std::optional<size_t> projection_index = {});
#endif  // DEBUG

// DeadOp is a special operation that can be used by analyzers to mark
// operations as being dead (typically, it should be used by calling the Graph's
// KillOperation method, which will Replace the old operation by a DeadOp).
// CopyingPhase and Analyzers should ignore Dead operations. A Dead operation
// should never be the input of a non-dead operation.
struct DeadOp : FixedArityOperationT<0, DeadOp> {
  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  auto options() const { return std::tuple{}; }
};

struct AbortCSADcheckOp : FixedArityOperationT<1, AbortCSADcheckOp> {
  static constexpr OpEffects effects =
      OpEffects().RequiredWhenUnused().CanLeaveCurrentFunction();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<String> message() const { return Base::input<String>(0); }

  explicit AbortCSADcheckOp(V<String> message) : Base(message) {}

  auto options() const { return std::tuple{}; }
};

struct GenericBinopOp : FixedArityOperationT<4, GenericBinopOp> {
#define GENERIC_BINOP_LIST(V) \
  V(Add)                      \
  V(Multiply)                 \
  V(Subtract)                 \
  V(Divide)                   \
  V(Modulus)                  \
  V(Exponentiate)             \
  V(BitwiseAnd)               \
  V(BitwiseOr)                \
  V(BitwiseXor)               \
  V(ShiftLeft)                \
  V(ShiftRight)               \
  V(ShiftRightLogical)        \
  V(Equal)                    \
  V(StrictEqual)              \
  V(LessThan)                 \
  V(LessThanOrEqual)          \
  V(GreaterThan)              \
  V(GreaterThanOrEqual)
  enum class Kind : uint8_t {
#define DEFINE_KIND(Name) k##Name,
    GENERIC_BINOP_LIST(DEFINE_KIND)
#undef DEFINE_KIND
  };
  Kind kind;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged())

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> left() const { return input<Object>(0); }
  V<Object> right() const { return input<Object>(1); }
  V<LazyFrameState> frame_state() const { return input<LazyFrameState>(2); }
  V<Context> context() const { return input<Context>(3); }

  GenericBinopOp(V<Object> left, V<Object> right, V<LazyFrameState> frame_state,
                 V<Context> context, Kind kind,
                 LazyDeoptOnThrow lazy_deopt_on_throw)
      : Base(left, right, frame_state, context),
        kind(kind),
        lazy_deopt_on_throw(lazy_deopt_on_throw) {}

  auto options() const { return std::tuple{kind, lazy_deopt_on_throw}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           GenericBinopOp::Kind kind);

struct GenericUnopOp : FixedArityOperationT<3, GenericUnopOp> {
#define GENERIC_UNOP_LIST(V) \
  V(BitwiseNot)              \
  V(Negate)                  \
  V(Increment)               \
  V(Decrement)
  enum class Kind : uint8_t {
#define DEFINE_KIND(Name) k##Name,
    GENERIC_UNOP_LIST(DEFINE_KIND)
#undef DEFINE_KIND
  };
  Kind kind;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged())

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }
  V<LazyFrameState> frame_state() const {
    return Base::input<LazyFrameState>(1);
  }
  V<Context> context() const { return Base::input<Context>(2); }

  GenericUnopOp(V<Object> input, V<LazyFrameState> frame_state,
                V<Context> context, Kind kind,
                LazyDeoptOnThrow lazy_deopt_on_throw)
      : Base(input, frame_state, context),
        kind(kind),
        lazy_deopt_on_throw(lazy_deopt_on_throw) {}

  auto options() const { return std::tuple{kind, lazy_deopt_on_throw}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           GenericUnopOp::Kind kind);

struct ToNumberOrNumericOp : FixedArityOperationT<3, ToNumberOrNumericOp> {
  Object::Conversion kind;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged())

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }
  V<LazyFrameState> frame_state() const {
    return Base::input<LazyFrameState>(1);
  }
  V<Context> context() const { return Base::input<Context>(2); }

  ToNumberOrNumericOp(V<Object> input, V<LazyFrameState> frame_state,
                      V<Context> context, Object::Conversion kind,
                      LazyDeoptOnThrow lazy_deopt_on_throw)
      : Base(input, frame_state, context),
        kind(kind),
        lazy_deopt_on_throw(lazy_deopt_on_throw) {}

  auto options() const { return std::tuple{kind, lazy_deopt_on_throw}; }
};

struct WordBinopOp : FixedArityOperationT<2, WordBinopOp> {
  enum class Kind : uint8_t {
    kAdd,
    kMul,
    kSignedMulOverflownBits,
    kUnsignedMulOverflownBits,
    kBitwiseAnd,
    kBitwiseOr,
    kBitwiseXor,
    kSub,
    kSignedDiv,
    kUnsignedDiv,
    kSignedMod,
    kUnsignedMod,
  };
  Kind kind;
  WordRepresentation rep;

  // We must avoid division by 0.
  static constexpr OpEffects effects = OpEffects().CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::PairOf(rep);
  }

  template <class WordType = Word>
  V<WordType> left() const
    requires(IsWord<WordType>())
  {
    return input<WordType>(0);
  }
  template <class WordType = Word>
  V<WordType> right() const
    requires(IsWord<WordType>())
  {
    return input<WordType>(1);
  }

  bool IsCommutative() const { return IsCommutative(kind); }

  static bool IsCommutative(Kind kind) {
    switch (kind) {
      case Kind::kAdd:
      case Kind::kMul:
      case Kind::kSignedMulOverflownBits:
      case Kind::kUnsignedMulOverflownBits:
      case Kind::kBitwiseAnd:
      case Kind::kBitwiseOr:
      case Kind::kBitwiseXor:
        return true;
      case Kind::kSub:
      case Kind::kSignedDiv:
      case Kind::kUnsignedDiv:
      case Kind::kSignedMod:
      case Kind::kUnsignedMod:
        return false;
    }
    UNREACHABLE();
  }

  static bool IsAssociative(Kind kind) {
    switch (kind) {
      case Kind::kAdd:
      case Kind::kMul:
      case Kind::kBitwiseAnd:
      case Kind::kBitwiseOr:
      case Kind::kBitwiseXor:
        return true;
      case Kind::kSignedMulOverflownBits:
      case Kind::kUnsignedMulOverflownBits:
      case Kind::kSub:
      case Kind::kSignedDiv:
      case Kind::kUnsignedDiv:
      case Kind::kSignedMod:
      case Kind::kUnsignedMod:
        return false;
    }
    UNREACHABLE();
  }
  // The Word32 and Word64 versions of the operator compute the same result when
  // truncated to 32 bit.
  static bool AllowsWord64ToWord32Truncation(Kind kind) {
    switch (kind) {
      case Kind::kAdd:
      case Kind::kMul:
      case Kind::kBitwiseAnd:
      case Kind::kBitwiseOr:
      case Kind::kBitwiseXor:
      case Kind::kSub:
        return true;
      case Kind::kSignedMulOverflownBits:
      case Kind::kUnsignedMulOverflownBits:
      case Kind::kSignedDiv:
      case Kind::kUnsignedDiv:
      case Kind::kSignedMod:
      case Kind::kUnsignedMod:
        return false;
    }
    UNREACHABLE();
  }

  WordBinopOp(V<Word> left, V<Word> right, Kind kind, WordRepresentation rep)
      : Base(left, right), kind(kind), rep(rep) {}

  auto options() const { return std::tuple{kind, rep}; }
  void PrintOptions(std::ostream& os) const;
};

struct FloatBinopOp : FixedArityOperationT<2, FloatBinopOp> {
  enum class Kind : uint8_t {
    kAdd,
    kMul,
    kMin,
    kMax,
    kSub,
    kDiv,
    kMod,
    kPower,
    kAtan2,
  };
  Kind kind;
  FloatRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::PairOf(rep);
  }

  V<Float> left() const { return input<Float>(0); }
  V<Float> right() const { return input<Float>(1); }

  static bool IsCommutative(Kind kind) {
    switch (kind) {
      case Kind::kAdd:
      case Kind::kMul:
      case Kind::kMin:
      case Kind::kMax:
        return true;
      case Kind::kSub:
      case Kind::kDiv:
      case Kind::kMod:
      case Kind::kPower:
      case Kind::kAtan2:
        return false;
    }
    UNREACHABLE();
  }

  FloatBinopOp(V<Float> left, V<Float> right, Kind kind,
               FloatRepresentation rep)
      : Base(left, right), kind(kind), rep(rep) {}

  void Validate(const Graph& graph) const {
    DCHECK_IMPLIES(kind == any_of(Kind::kPower, Kind::kAtan2, Kind::kMod),
                   rep == FloatRepresentation::Float64());
  }
  auto options() const { return std::tuple{kind, rep}; }
  void PrintOptions(std::ostream& os) const;
};

struct Word32PairBinopOp : FixedArityOperationT<4, Word32PairBinopOp> {
  enum class Kind : uint8_t {
    kAdd,
    kSub,
    kMul,
    kShiftLeft,
    kShiftRightArithmetic,
    kShiftRightLogical,
  };
  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32(),
                     RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      const ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> left_low() const { return input<Word32>(0); }
  V<Word32> left_high() const { return input<Word32>(1); }
  V<Word32> right_low() const { return input<Word32>(2); }
  V<Word32> right_high() const { return input<Word32>(3); }

  Word32PairBinopOp(V<Word32> left_low, V<Word32> left_high,
                    V<Word32> right_low, V<Word32> right_high, Kind kind)
      : Base(left_low, left_high, right_low, right_high), kind(kind) {}

  auto options() const { return std::tuple{kind}; }
  void PrintOptions(std::ostream& os) const;
};

// Multiplies 2 Word64 values and produces a 128-bit result in 2 Word64 values
struct Word64MulWideOp : FixedArityOperationT<2, Word64MulWideOp> {
  enum class Kind : uint8_t { kSigned, kUnsigned };
  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word64(),
                     RegisterRepresentation::Word64()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word64(),
                          MaybeRegisterRepresentation::Word64()>();
  }

  V<Word64> left() const { return input<Word64>(0); }
  V<Word64> right() const { return input<Word64>(1); }

  Word64MulWideOp(V<Word64> left, V<Word64> right, Kind kind)
      : Base(left, right), kind(kind) {}

  auto options() const { return std::tuple{kind}; }
  void PrintOptions(std::ostream& os) const;
};

struct Word64AddSub128BinopOp
    : FixedArityOperationT<4, Word64AddSub128BinopOp> {
  enum class Kind : uint8_t { kAdd, kSub };
  Kind kind;
  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word64(),
                     RegisterRepresentation::Word64()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      const ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word64(),
                          MaybeRegisterRepresentation::Word64(),
                          MaybeRegisterRepresentation::Word64(),
                          MaybeRegisterRepresentation::Word64()>();
  }

  V<Word64> left_low() const { return input<Word64>(0); }
  V<Word64> left_high() const { return input<Word64>(1); }
  V<Word64> right_low() const { return input<Word64>(2); }
  V<Word64> right_high() const { return input<Word64>(3); }

  Word64AddSub128BinopOp(V<Word64> al, V<Word64> ah, V<Word64> bl, V<Word64> bh,
                         Kind kind)
      : Base(al, ah, bl, bh), kind(kind) {}

  auto options() const { return std::tuple{kind}; }
  void PrintOptions(std::ostream& os) const;
};

struct WordBinopDeoptOnOverflowOp
    : FixedArityOperationT<3, WordBinopDeoptOnOverflowOp> {
  enum class Kind : uint8_t {
    kSignedAdd,
    kSignedMul,
    kSignedSub,
    kSignedDiv,
    kSignedMod,
    kUnsignedDiv,
    kUnsignedMod
  };
  Kind kind;
  WordRepresentation rep;
  FeedbackSource feedback;
  CheckForMinusZeroMode mode;

  static bool IsCommutative(Kind kind) {
    switch (kind) {
      case Kind::kSignedAdd:
      case Kind::kSignedMul:
        return true;
      case Kind::kSignedSub:
      case Kind::kSignedDiv:
      case Kind::kSignedMod:
      case Kind::kUnsignedDiv:
      case Kind::kUnsignedMod:
        return false;
    }
    UNREACHABLE();
  }

  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::PairOf(rep);
  }

  template <IsWordT WordType = Word>
  V<WordType> left() const {
    return input<WordType>(0);
  }
  template <IsWordT WordType = Word>
  V<WordType> right() const {
    return input<WordType>(1);
  }
  V<EagerFrameState> frame_state() const { return input<EagerFrameState>(2); }

  WordBinopDeoptOnOverflowOp(V<Word> left, V<Word> right,
                             V<EagerFrameState> frame_state, Kind kind,
                             WordRepresentation rep, FeedbackSource feedback,
                             CheckForMinusZeroMode mode)
      : Base(left, right, frame_state),
        kind(kind),
        rep(rep),
        feedback(feedback),
        mode(mode) {}

  void Validate(const Graph& graph) const {
    DCHECK_IMPLIES(kind == Kind::kUnsignedDiv || kind == Kind::kUnsignedMod,
                   rep == WordRepresentation::Word32());
  }
  auto options() const { return std::tuple{kind, rep, feedback, mode}; }
  void PrintOptions(std::ostream& os) const;
};

struct OverflowCheckedBinopOp
    : FixedArityOperationT<2, OverflowCheckedBinopOp> {
  static constexpr int kValueIndex = 0;
  static constexpr int kOverflowIndex = 1;

  enum class Kind : uint8_t {
    kSignedAdd,
    kSignedMul,
    kSignedSub,
  };
  Kind kind;
  WordRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (rep.value()) {
      case WordRepresentation::Word32():
        return RepVector<RegisterRepresentation::Word32(),
                         RegisterRepresentation::Word32()>();
      case WordRepresentation::Word64():
        return RepVector<RegisterRepresentation::Word64(),
                         RegisterRepresentation::Word32()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::PairOf(rep);
  }

  V<Word> left() const { return input<Word>(0); }
  V<Word> right() const { return input<Word>(1); }

  static bool IsCommutative(Kind kind) {
    switch (kind) {
      case Kind::kSignedAdd:
      case Kind::kSignedMul:
        return true;
      case Kind::kSignedSub:
        return false;
    }
    UNREACHABLE();
  }

  OverflowCheckedBinopOp(V<Word> left, V<Word> right, Kind kind,
                         WordRepresentation rep)
      : Base(left, right), kind(kind), rep(rep) {}

  auto options() const { return std::tuple{kind, rep}; }
  void PrintOptions(std::ostream& os) const;
};

struct WordUnaryOp : FixedArityOperationT<1, WordUnaryOp> {
  enum class Kind : uint8_t {
    kReverseBytes,
    kCountLeadingZeros,
    kCountTrailingZeros,
    kPopCount,
    kSignExtend8,
    kSignExtend16,
  };
  Kind kind;
  WordRepresentation rep;
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  V<Word> input() const { return Base::input<Word>(0); }

  V8_EXPORT_PRIVATE static bool IsSupported(Kind kind, WordRepresentation rep);

  explicit WordUnaryOp(V<Word> input, Kind kind, WordRepresentation rep)
      : Base(input), kind(kind), rep(rep) {}

  auto options() const { return std::tuple{kind, rep}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           WordUnaryOp::Kind kind);

struct OverflowCheckedUnaryOp
    : FixedArityOperationT<1, OverflowCheckedUnaryOp> {
  static constexpr int kValueIndex = 0;
  static constexpr int kOverflowIndex = 1;

  enum class Kind : uint8_t { kAbs };
  Kind kind;
  WordRepresentation rep;
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (rep.value()) {
      case WordRepresentation::Word32():
        return RepVector<RegisterRepresentation::Word32(),
                         RegisterRepresentation::Word32()>();
      case WordRepresentation::Word64():
        return RepVector<RegisterRepresentation::Word64(),
                         RegisterRepresentation::Word32()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  V<Word> input() const { return Base::input<Word>(0); }

  explicit OverflowCheckedUnaryOp(V<Word> input, Kind kind,
                                  WordRepresentation rep)
      : Base(input), kind(kind), rep(rep) {}

  auto options() const { return std::tuple{kind, rep}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           OverflowCheckedUnaryOp::Kind kind);

struct FloatUnaryOp : FixedArityOperationT<1, FloatUnaryOp> {
  enum class Kind : uint8_t {
    kAbs,
    kNegate,
    kSilenceNaN,
    kRoundDown,      // round towards -infinity
    kRoundUp,        // round towards +infinity
    kRoundToZero,    // round towards 0
    kRoundTiesEven,  // break ties by rounding towards the next even number
    kLog,
    kLog2,
    kLog10,
    kLog1p,
    kSqrt,
    kCbrt,
    kExp,
    kExpm1,
    kSin,
    kCos,
    kSinh,
    kCosh,
    kAcos,
    kAsin,
    kAsinh,
    kAcosh,
    kTan,
    kTanh,
    kAtan,
    kAtanh,
  };

  Kind kind;
  FloatRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  V<Float> input() const { return Base::input<Float>(0); }

  V8_EXPORT_PRIVATE static bool IsSupported(Kind kind, FloatRepresentation rep);

  explicit FloatUnaryOp(V<Float> input, Kind kind, FloatRepresentation rep)
      : Base(input), kind(kind), rep(rep) {}

  auto options() const { return std::tuple{kind, rep}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           FloatUnaryOp::Kind kind);

struct ShiftOp : FixedArityOperationT<2, ShiftOp> {
  enum class Kind : uint8_t {
    kShiftRightArithmeticShiftOutZeros,
    kShiftRightArithmetic,
    kShiftRightLogical,
    kShiftLeft,
    kRotateRight,
    kRotateLeft
  };
  Kind kind;
  WordRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(static_cast<const RegisterRepresentation*>(&rep), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(storage,
                        {static_cast<const RegisterRepresentation&>(rep),
                         RegisterRepresentation::Word32()});
  }

  template <IsWordT WordT = Word>
  V<WordT> left() const {
    DCHECK(IsValidTypeFor<WordT>(rep));
    return input<WordT>(0);
  }
  V<Word32> right() const { return input<Word32>(1); }

  bool IsRightShift() const { return IsRightShift(kind); }

  static bool IsRightShift(Kind kind) {
    switch (kind) {
      case Kind::kShiftRightArithmeticShiftOutZeros:
      case Kind::kShiftRightArithmetic:
      case Kind::kShiftRightLogical:
        return true;
      case Kind::kShiftLeft:
      case Kind::kRotateRight:
      case Kind::kRotateLeft:
        return false;
    }
    UNREACHABLE();
  }
  // The Word32 and Word64 versions of the operator compute the same result when
  // truncated to 32 bit.
  static bool AllowsWord64ToWord32Truncation(Kind kind) {
    switch (kind) {
      case Kind::kShiftLeft:
        return true;
      case Kind::kShiftRightArithmeticShiftOutZeros:
      case Kind::kShiftRightArithmetic:
      case Kind::kShiftRightLogical:
      case Kind::kRotateRight:
      case Kind::kRotateLeft:
        return false;
    }
    UNREACHABLE();
  }

  ShiftOp(V<Word> left, V<Word32> right, Kind kind, WordRepresentation rep)
      : Base(left, right), kind(kind), rep(rep) {}

  auto options() const { return std::tuple{kind, rep}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ShiftOp::Kind kind);

struct ComparisonOp : FixedArityOperationT<2, ComparisonOp> {
  enum class Kind : uint8_t {
    kEqual,
    kSignedLessThan,
    kSignedLessThanOrEqual,
    kUnsignedLessThan,
    kUnsignedLessThanOrEqual
  };
  Kind kind;
  RegisterRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::PairOf(rep);
  }

  static bool IsCommutative(Kind kind) { return kind == Kind::kEqual; }

  template <typename T = Any>
  V<T> left() const {
    DCHECK(IsValidTypeFor<T>(rep));
    return input<T>(0);
  }
  template <typename T = Any>
  V<T> right() const {
    DCHECK(IsValidTypeFor<T>(rep));
    return input<T>(1);
  }

  ComparisonOp(V<Any> left, V<Any> right, Kind kind, RegisterRepresentation rep)
      : Base(left, right), kind(kind), rep(rep) {}

  void Validate(const Graph& graph) const {
    if (kind == Kind::kEqual) {
      DCHECK_EQ(rep, any_of(RegisterRepresentation::Word32(),
                            RegisterRepresentation::Word64(),
                            RegisterRepresentation::Float32(),
                            RegisterRepresentation::Float64(),
                            RegisterRepresentation::Tagged()));

      RegisterRepresentation input_rep = rep;
#ifdef V8_COMPRESS_POINTERS
      // In the presence of pointer compression, we only compare the lower
      // 32bit.
      if (input_rep == RegisterRepresentation::Tagged()) {
        input_rep = RegisterRepresentation::Compressed();
      }
#endif  // V8_COMPRESS_POINTERS
#ifdef DEBUG
      ValidateOpInputRep(graph, left(), input_rep);
      ValidateOpInputRep(graph, right(), input_rep);
#endif  // DEBUG
      USE(input_rep);
    } else {
      DCHECK_EQ(rep, any_of(RegisterRepresentation::Word32(),
                            RegisterRepresentation::Word64(),
                            RegisterRepresentation::Float32(),
                            RegisterRepresentation::Float64()));
      DCHECK_IMPLIES(
          rep == any_of(RegisterRepresentation::Float32(),
                        RegisterRepresentation::Float64()),
          kind == any_of(Kind::kSignedLessThan, Kind::kSignedLessThanOrEqual));
    }
  }
  auto options() const { return std::tuple{kind, rep}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ComparisonOp::Kind kind);
DEFINE_MULTI_SWITCH_INTEGRAL(ComparisonOp::Kind, 8)

struct ChangeOp : FixedArityOperationT<1, ChangeOp> {
  enum class Kind : uint8_t {
    // convert between different floating-point types. Note that the
    // Float64->Float32 conversion is truncating.
    kFloatConversion,
    // overflow guaranteed to result in the minimal integer
    kSignedFloatTruncateOverflowToMin,
    kUnsignedFloatTruncateOverflowToMin,
    // JS semantics float64 to word32 truncation
    // https://tc39.es/ecma262/#sec-touint32
    kJSFloatTruncate,
    // convert float64 to float16, then bitcast word32. Used for storing into
    // Float16Array and Math.fround16.
    kJSFloat16TruncateWithBitcast,
    // bitcast word32 to float16 and convert to float64. Used for loading from
    // Float16Array and Math.fround16.
    kJSFloat16ChangeWithBitcast,
    // convert (un)signed integer to floating-point value
    kSignedToFloat,
    kUnsignedToFloat,
    // extract half of a float64 value
    kExtractHighHalf,
    kExtractLowHalf,
    // increase bit-width for unsigned integer values
    kZeroExtend,
    // increase bid-width for signed integer values
    kSignExtend,
    // truncate word64 to word32
    kTruncate,
    // preserve bits, change meaning
    kBitcast
  };
  // Violated assumptions result in undefined behavior.
  enum class Assumption : uint8_t {
    kNoAssumption,
    // Used for conversions from floating-point to integer, assumes that the
    // value doesn't exceed the integer range.
    kNoOverflow,
    // Assume that the original value can be recovered by a corresponding
    // reverse transformation.
    kReversible,
  };
  Kind kind;
  // Reversible means undefined behavior if value cannot be represented
  // precisely.
  Assumption assumption;
  RegisterRepresentation from;
  RegisterRepresentation to;

  // Returns true if change<kind>(change<reverse_kind>(a)) == a for all a.
  // This assumes that change<reverse_kind> uses the inverted {from} and {to}
  // representations, i.e. the input to the inner change op has the same
  // representation as the result of the outer change op.
  static bool IsReversible(Kind kind, Assumption assumption,
                           RegisterRepresentation from,
                           RegisterRepresentation to, Kind reverse_kind,
                           bool signalling_nan_possible) {
    switch (kind) {
      case Kind::kFloatConversion:
        return from == RegisterRepresentation::Float32() &&
               to == RegisterRepresentation::Float64() &&
               reverse_kind == Kind::kFloatConversion &&
               !signalling_nan_possible;
      case Kind::kSignedFloatTruncateOverflowToMin:
        return assumption == Assumption::kReversible &&
               reverse_kind == Kind::kSignedToFloat;
      case Kind::kUnsignedFloatTruncateOverflowToMin:
        return assumption == Assumption::kReversible &&
               reverse_kind == Kind::kUnsignedToFloat;
      case Kind::kJSFloatTruncate:
        return false;
      case Kind::kJSFloat16TruncateWithBitcast:
      case Kind::kJSFloat16ChangeWithBitcast:
        return false;
      case Kind::kSignedToFloat:
        if (from == RegisterRepresentation::Word32() &&
            to == RegisterRepresentation::Float64()) {
          return reverse_kind == any_of(Kind::kSignedFloatTruncateOverflowToMin,
                                        Kind::kJSFloatTruncate);
        } else {
          return assumption == Assumption::kReversible &&
                 reverse_kind ==
                     any_of(Kind::kSignedFloatTruncateOverflowToMin);
        }
      case Kind::kUnsignedToFloat:
        if (from == RegisterRepresentation::Word32() &&
            to == RegisterRepresentation::Float64()) {
          return reverse_kind ==
                 any_of(Kind::kUnsignedFloatTruncateOverflowToMin,
                        Kind::kJSFloatTruncate);
        } else {
          return assumption == Assumption::kReversible &&
                 reverse_kind == Kind::kUnsignedFloatTruncateOverflowToMin;
        }
      case Kind::kExtractHighHalf:
      case Kind::kExtractLowHalf:
        return false;
      case Kind::kZeroExtend:
      case Kind::kSignExtend:
        DCHECK_EQ(from, RegisterRepresentation::Word32());
        DCHECK_EQ(to, RegisterRepresentation::Word64());
        return reverse_kind == Kind::kTruncate;
      case Kind::kTruncate:
        DCHECK_EQ(from, RegisterRepresentation::Word64());
        DCHECK_EQ(to, RegisterRepresentation::Word32());
        return reverse_kind == Kind::kBitcast;
      case Kind::kBitcast:
        return reverse_kind == Kind::kBitcast;
    }
    UNREACHABLE();
  }

  bool IsReversibleBy(Kind reverse_kind, bool signalling_nan_possible) const {
    return IsReversible(kind, assumption, from, to, reverse_kind,
                        signalling_nan_possible);
  }

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&to, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(from);
  }

  template <typename Type = Untagged>
    requires IsUntagged<Type>
  V<Type> input() const {
    DCHECK(IsValidTypeFor<Type>(from));
    return Base::input<Type>(0);
  }

  ChangeOp(V<Untagged> input, Kind kind, Assumption assumption,
           RegisterRepresentation from, RegisterRepresentation to)
      : Base(input), kind(kind), assumption(assumption), from(from), to(to) {}

  void Validate(const Graph& graph) const {
    DCHECK_NE(from, RegisterRepresentation::Tagged());
    DCHECK_NE(to, RegisterRepresentation::Tagged());
    // Bitcasts from and to Tagged should use a TaggedBitcast instead (which has
    // different effects, since it's unsafe to reorder such bitcasts accross
    // GCs).
  }
  auto options() const { return std::tuple{kind, assumption, from, to}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ChangeOp::Kind kind);
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ChangeOp::Assumption assumption);
DEFINE_MULTI_SWITCH_INTEGRAL(ChangeOp::Kind, 16)
DEFINE_MULTI_SWITCH_INTEGRAL(ChangeOp::Assumption, 4)

struct ChangeOrDeoptOp : FixedArityOperationT<2, ChangeOrDeoptOp> {
  enum class Kind : uint8_t {
    kUint32ToInt32,
    kInt64ToInt32,
    kInt64ToAdditiveSafeInteger,
    kUint64ToInt32,
    kUint64ToInt64,
    kFloat64ToInt32,
    kFloat64ToUint32,
    kInt32ToUint64,
    kInt64ToUint64,
    kFloat64ToUint64,
    kFloat64ToAdditiveSafeInteger,
    kFloat64ToInt64,
    kFloat64NotHole,
  };
  Kind kind;
  CheckForMinusZeroMode minus_zero_mode;
  FeedbackSource feedback;

  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (kind) {
      case Kind::kUint32ToInt32:
      case Kind::kInt64ToInt32:
      case Kind::kUint64ToInt32:
      case Kind::kFloat64ToInt32:
      case Kind::kFloat64ToUint32:
        return RepVector<RegisterRepresentation::Word32()>();
      case Kind::kUint64ToInt64:
      case Kind::kInt64ToAdditiveSafeInteger:
      case Kind::kFloat64ToAdditiveSafeInteger:
      case Kind::kFloat64ToInt64:
      case Kind::kInt32ToUint64:
      case Kind::kInt64ToUint64:
      case Kind::kFloat64ToUint64:
        return RepVector<RegisterRepresentation::Word64()>();
      case Kind::kFloat64NotHole:
        return RepVector<RegisterRepresentation::Float64()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    switch (kind) {
      case Kind::kUint32ToInt32:
      case Kind::kInt32ToUint64:
        return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
      case Kind::kInt64ToInt32:
      case Kind::kInt64ToAdditiveSafeInteger:
      case Kind::kInt64ToUint64:
      case Kind::kUint64ToInt32:
      case Kind::kUint64ToInt64:
        return MaybeRepVector<MaybeRegisterRepresentation::Word64()>();
      case Kind::kFloat64ToInt32:
      case Kind::kFloat64ToUint32:
      case Kind::kFloat64ToAdditiveSafeInteger:
      case Kind::kFloat64ToInt64:
      case Kind::kFloat64ToUint64:
      case Kind::kFloat64NotHole:
        return MaybeRepVector<MaybeRegisterRepresentation::Float64()>();
    }
    UNREACHABLE();
  }

  V<Untagged> input() const { return Base::input<Untagged>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }

  ChangeOrDeoptOp(V<Untagged> input, V<EagerFrameState> frame_state, Kind kind,
                  CheckForMinusZeroMode minus_zero_mode,
                  const FeedbackSource& feedback)
      : Base(input, frame_state),
        kind(kind),
        minus_zero_mode(minus_zero_mode),
        feedback(feedback) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple{kind, minus_zero_mode, feedback}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ChangeOrDeoptOp::Kind kind);

// Perform a conversion and return a pair of the result and a bit if it was
// successful.
struct TryChangeOp : FixedArityOperationT<1, TryChangeOp> {
  static constexpr uint32_t kSuccessValue = 1;
  static constexpr uint32_t kFailureValue = 0;
  enum class Kind : uint8_t {
    // The result of the truncation is undefined if the result is out of range.
    kSignedFloatTruncateOverflowUndefined,
    kUnsignedFloatTruncateOverflowUndefined,
  };
  Kind kind;
  FloatRepresentation from;
  WordRepresentation to;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (to.value()) {
      case WordRepresentation::Word32():
        return RepVector<RegisterRepresentation::Word32(),
                         RegisterRepresentation::Word32()>();
      case WordRepresentation::Word64():
        return RepVector<RegisterRepresentation::Word64(),
                         RegisterRepresentation::Word32()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(from);
  }

  V<Float> input() const { return Base::input<Float>(0); }

  TryChangeOp(V<Float> input, Kind kind, FloatRepresentation from,
              WordRepresentation to)
      : Base(input), kind(kind), from(from), to(to) {}

  auto options() const { return std::tuple{kind, from, to}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           TryChangeOp::Kind kind);

struct BitcastWord32PairToFloat64Op
    : FixedArityOperationT<2, BitcastWord32PairToFloat64Op> {
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Float64()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> high_word32() const { return input<Word32>(0); }
  V<Word32> low_word32() const { return input<Word32>(1); }

  BitcastWord32PairToFloat64Op(V<Word32> high_word32, V<Word32> low_word32)
      : Base(high_word32, low_word32) {}

  auto options() const { return std::tuple{}; }
};

struct TaggedBitcastOp : FixedArityOperationT<1, TaggedBitcastOp> {
  enum class Kind : uint8_t {
    kSmi,  // This is a bitcast from a Word to a Smi or from a Smi to a Word
    kHeapObject,     // This is a bitcast from or to a Heap Object
    kTagAndSmiBits,  // This is a bitcast where only access to the tag and the
                     // smi bits (if it's a smi) are valid
    kAny
  };
  Kind kind;
  RegisterRepresentation from;
  RegisterRepresentation to;

  OpEffects Effects() const {
    switch (kind) {
      case Kind::kSmi:
      case Kind::kTagAndSmiBits:
        return OpEffects();
      case Kind::kHeapObject:
      case Kind::kAny:
        // Due to moving GC, converting from or to pointers doesn't commute with
        // GC.
        return OpEffects().CanDoRawHeapAccess();
    }
    UNREACHABLE();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&to, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(from);
  }

  OpIndex input() const { return Base::input(0); }

  TaggedBitcastOp(OpIndex input, RegisterRepresentation from,
                  RegisterRepresentation to, Kind kind)
      : Base(input), kind(kind), from(from), to(to) {}

  V8_EXPORT_PRIVATE void Validate(const Graph& graph) const;
  auto options() const { return std::tuple{from, to, kind}; }
};
std::ostream& operator<<(std::ostream& os, TaggedBitcastOp::Kind assumption);

struct SelectOp : FixedArityOperationT<3, SelectOp> {
  enum class Implementation : uint8_t { kAny, kForceBranch, kForceCMove };

  RegisterRepresentation rep;
  BranchHint hint;
  Implementation implem;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&rep, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(storage, {RegisterRepresentation::Word32(), rep, rep});
  }

  SelectOp(V<Word32> cond, V<Any> vtrue, V<Any> vfalse,
           RegisterRepresentation rep, BranchHint hint, Implementation implem)
      : Base(cond, vtrue, vfalse), rep(rep), hint(hint), implem(implem) {}

  void Validate(const Graph& graph) const {
#ifdef DEBUG
    if (implem == Implementation::kForceCMove) {
      switch (rep.value()) {
        case RegisterRepresentation::Enum::kWord32:
          DCHECK(SupportedOperations::word32_select());
          break;
        case RegisterRepresentation::Enum::kWord64:
          DCHECK(SupportedOperations::word64_select());
          break;
        case RegisterRepresentation::Enum::kFloat32:
          DCHECK(SupportedOperations::float32_select());
          break;
        case RegisterRepresentation::Enum::kFloat64:
          DCHECK(SupportedOperations::float64_select());
          break;

        case RegisterRepresentation::Enum::kCompressed:
        case RegisterRepresentation::Enum::kTagged:
          FATAL("CMove for Tagged/Compressed values isn't implemented yet");

        case RegisterRepresentation::Enum::kSimd128:
        case RegisterRepresentation::Enum::kSimd256:
          FATAL("CMove for Simd register not supported");
      }
    }
#endif
  }

  V<Word32> cond() const { return input<Word32>(0); }
  V<Any> vtrue() const { return input<Any>(1); }
  V<Any> vfalse() const { return input<Any>(2); }

  auto options() const { return std::tuple{rep, hint, implem}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           SelectOp::Implementation kind);

struct PhiOp : OperationT<PhiOp> {
  RegisterRepresentation rep;

  // Phis have to remain at the beginning of the current block. As effects
  // cannot express this completely, we just mark them as having no effects but
  // treat them specially when scheduling operations.
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&rep, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    storage.resize(input_count);
    for (size_t i = 0; i < input_count; ++i) {
      storage[i] = rep;
    }
    return base::VectorOf(storage);
  }

  explicit PhiOp(base::Vector<const OpIndex> inputs, RegisterRepresentation rep)
      : Base(CheckPhiInputCount(inputs)), rep(rep) {}

  static V8_INLINE base::Vector<const OpIndex> CheckPhiInputCount(
      base::Vector<const OpIndex> inputs) {
    // We check the phi input count explicitly to prevent Wasm from passing too
    // many inputs, e.g. as part of a br_table operation.
    if (V8_UNLIKELY(inputs.size() >
                    std::numeric_limits<decltype(PhiOp::input_count)>::max())) {
      V8::FatalProcessOutOfMemory(nullptr, "Too many phi inputs");
    }
    return inputs;
  }

  // Helpers to access loop phis forward/backedge. These should only be used for
  // loop phis (which isn't trivial to DCHECK since PhiOp doesn't know if it's a
  // loop phi or not).
  static constexpr size_t kLoopPhiForwardEdgeIndex = 0;
  static constexpr size_t kLoopPhiBackEdgeIndex = 1;
  static constexpr size_t kLoopPhiInputCount = 2;
  V<Any> forward_edge() const { return input<Any>(kLoopPhiForwardEdgeIndex); }
  V<Any> back_edge() const { return input<Any>(kLoopPhiBackEdgeIndex); }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    auto mapped_inputs = mapper.template Map<64>(inputs());
    return fn(base::VectorOf(mapped_inputs), rep);
  }

  void Validate(const Graph& graph) const { DCHECK_GT(input_count, 0); }
  auto options() const { return std::tuple{rep}; }
};

// Used as a placeholder for a loop-phi while building the graph, replaced with
// a normal `PhiOp` before graph building is over, so it should never appear in
// a complete graph.
struct PendingLoopPhiOp : FixedArityOperationT<1, PendingLoopPhiOp> {
  RegisterRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&rep, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  OpIndex first() const { return input(0); }
  PendingLoopPhiOp(OpIndex first, RegisterRepresentation rep)
      : Base(first), rep(rep) {}

  auto options() const { return std::tuple{rep}; }
};

struct ConstantOp : FixedArityOperationT<0, ConstantOp> {
  enum class Kind : uint8_t {
    kWord32,
    kWord64,
    kFloat32,
    kFloat64,
    kSmi,
    kNumber,  // TODO(tebbi): See if we can avoid number constants.
    kTaggedIndex,
    kExternal,
    kHeapObject,
    kCompressedHeapObject,
    kTrustedHeapObject,
    kRelocatableWasmCall,
    kRelocatableWasmStubCall,
    kRelocatableWasmIndirectCallTarget,
    kRelocatableWasmCanonicalSignatureId,
    kRelocatableWasmCodePointer
  };

  Kind kind;
  RegisterRepresentation rep = Representation(kind);
  union Storage {
    uint64_t integral;
    i::Float32 float32;
    i::Float64 float64;
    ExternalReference external;
    IndirectHandle<HeapObject> handle;

    Storage(uint64_t integral = 0) : integral(integral) {}
    Storage(i::Tagged<Smi> smi) : integral(smi.ptr()) {}
    Storage(i::Float64 constant) : float64(constant) {}
    Storage(i::Float32 constant) : float32(constant) {}
    Storage(ExternalReference constant) : external(constant) {}
    Storage(IndirectHandle<HeapObject> constant) : handle(constant) {}

    inline bool operator==(const ConstantOp::Storage&) const {
      // It is tricky to implement this properly. We currently need to define
      // this for the matchers, but this should never be called.
      UNREACHABLE();
    }
  } storage;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&rep, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>&) const {
    return {};
  }

  static RegisterRepresentation Representation(Kind kind) {
    switch (kind) {
      case Kind::kRelocatableWasmCanonicalSignatureId:
      case Kind::kWord32:
        return RegisterRepresentation::Word32();
      case Kind::kWord64:
        return RegisterRepresentation::Word64();
      case Kind::kFloat32:
        return RegisterRepresentation::Float32();
      case Kind::kFloat64:
        return RegisterRepresentation::Float64();
      case Kind::kExternal:
      case Kind::kTaggedIndex:
      case Kind::kTrustedHeapObject:
      case Kind::kRelocatableWasmCall:
      case Kind::kRelocatableWasmStubCall:
      case Kind::kRelocatableWasmCodePointer:
        return RegisterRepresentation::WordPtr();
      case Kind::kRelocatableWasmIndirectCallTarget:
        return RegisterRepresentation::Word32();
      case Kind::kSmi:
      case Kind::kHeapObject:
      case Kind::kNumber:
        return RegisterRepresentation::Tagged();
      case Kind::kCompressedHeapObject:
        return RegisterRepresentation::Compressed();
    }
    UNREACHABLE();
  }

  ConstantOp(Kind kind, Storage storage)
      : Base(), kind(kind), storage(storage) {}

  void Validate(const Graph& graph) const {
    DCHECK_IMPLIES(
        kind == Kind::kWord32,
        storage.integral <= WordRepresentation::Word32().MaxUnsignedValue());
    DCHECK_IMPLIES(
        kind == Kind::kRelocatableWasmCanonicalSignatureId,
        storage.integral <= WordRepresentation::Word32().MaxSignedValue());
  }

  uint64_t integral() const {
    DCHECK(IsIntegral());
    return storage.integral;
  }

  int64_t signed_integral() const {
    DCHECK(IsIntegral());
    switch (kind) {
      case Kind::kWord32:
      case Kind::kRelocatableWasmCanonicalSignatureId:
        return static_cast<int32_t>(storage.integral);
      case Kind::kWord64:
        return static_cast<int64_t>(storage.integral);
      default:
        UNREACHABLE();
    }
  }

  uint32_t word32() const {
    DCHECK(kind == Kind::kWord32 || kind == Kind::kWord64);
    return static_cast<uint32_t>(storage.integral);
  }

  uint64_t word64() const {
    DCHECK_EQ(kind, Kind::kWord64);
    return static_cast<uint64_t>(storage.integral);
  }

  i::Tagged<Smi> smi() const {
    DCHECK_EQ(kind, Kind::kSmi);
    return i::Tagged<Smi>(storage.integral);
  }

  i::Float64 number() const {
    DCHECK_EQ(kind, Kind::kNumber);
    return storage.float64;
  }

  i::Float32 float32() const {
    DCHECK_EQ(kind, Kind::kFloat32);
    return storage.float32;
  }

  i::Float64 float64() const {
    DCHECK_EQ(kind, Kind::kFloat64);
    return storage.float64;
  }

  int32_t tagged_index() const {
    DCHECK_EQ(kind, Kind::kTaggedIndex);
    return static_cast<int32_t>(static_cast<uint32_t>(storage.integral));
  }

  ExternalReference external_reference() const {
    DCHECK_EQ(kind, Kind::kExternal);
    return storage.external;
  }

  IndirectHandle<i::HeapObject> handle() const {
    DCHECK(kind == Kind::kHeapObject || kind == Kind::kCompressedHeapObject ||
           kind == Kind::kTrustedHeapObject);
    return storage.handle;
  }

  bool IsWord(uint64_t value) const {
    switch (kind) {
      case Kind::kWord32:
        return static_cast<uint32_t>(value) == word32();
      case Kind::kWord64:
        return value == word64();
      default:
        UNREACHABLE();
    }
  }

  bool IsIntegral() const {
    return kind == Kind::kWord32 || kind == Kind::kWord64 ||
           kind == Kind::kRelocatableWasmCall ||
           kind == Kind::kRelocatableWasmStubCall ||
           kind == Kind::kRelocatableWasmCanonicalSignatureId ||
           kind == Kind::kRelocatableWasmIndirectCallTarget ||
           kind == Kind::kRelocatableWasmCodePointer;
  }

  auto options() const { return std::tuple{kind, storage}; }

  void PrintOptions(std::ostream& os) const;
  size_t hash_value(
      HashingStrategy strategy = HashingStrategy::kDefault) const {
    switch (kind) {
      case Kind::kWord32:
      case Kind::kWord64:
      case Kind::kSmi:
      case Kind::kTaggedIndex:
      case Kind::kRelocatableWasmCall:
      case Kind::kRelocatableWasmStubCall:
      case Kind::kRelocatableWasmIndirectCallTarget:
      case Kind::kRelocatableWasmCanonicalSignatureId:
      case Kind::kRelocatableWasmCodePointer:
        return HashWithOptions(storage.integral);
      case Kind::kFloat32:
        return HashWithOptions(storage.float32.get_bits());
      case Kind::kFloat64:
      case Kind::kNumber:
        return HashWithOptions(storage.float64.get_bits());
      case Kind::kExternal:
        return HashWithOptions(strategy == HashingStrategy::kMakeSnapshotStable
                                   ? 0
                                   : storage.external.raw());
      case Kind::kHeapObject:
      case Kind::kCompressedHeapObject:
      case Kind::kTrustedHeapObject:
        if (strategy == HashingStrategy::kMakeSnapshotStable) {
          return HashWithOptions();
        }
        return HashWithOptions(storage.handle.address());
    }
    UNREACHABLE();
  }
  bool operator==(const ConstantOp& other) const {
    if (kind != other.kind) return false;
    switch (kind) {
      case Kind::kWord32:
      case Kind::kWord64:
      case Kind::kSmi:
      case Kind::kTaggedIndex:
      case Kind::kRelocatableWasmCall:
      case Kind::kRelocatableWasmStubCall:
      case Kind::kRelocatableWasmCanonicalSignatureId:
      case Kind::kRelocatableWasmIndirectCallTarget:
      case Kind::kRelocatableWasmCodePointer:
        return storage.integral == other.storage.integral;
      case Kind::kFloat32:
        // Using a bit_cast to uint32_t in order to return false when comparing
        // +0 and -0.
        // Note: for JavaScript, it would be fine to return true when both
        // values are NaNs, but for Wasm we must not merge NaNs that way.
        // Since we canonicalize NaNs for JS anyway, we don't need to treat
        // them specially here.
        return base::bit_cast<uint32_t>(storage.float32) ==
               base::bit_cast<uint32_t>(other.storage.float32);
      case Kind::kFloat64:
      case Kind::kNumber:
        // Using a bit_cast to uint64_t in order to return false when comparing
        // +0 and -0.
        // Note: for JavaScript, it would be fine to return true when both
        // values are NaNs, but for Wasm we must not merge NaNs that way.
        // Since we canonicalize NaNs for JS anyway, we don't need to treat
        // them specially here.
        return base::bit_cast<uint64_t>(storage.float64) ==
               base::bit_cast<uint64_t>(other.storage.float64);
      case Kind::kExternal:
        return storage.external.raw() == other.storage.external.raw();
      case Kind::kHeapObject:
      case Kind::kCompressedHeapObject:
      case Kind::kTrustedHeapObject:
        return storage.handle.address() == other.storage.handle.address();
    }
    UNREACHABLE();
  }

  bool IsRelocatable() const {
    return kind == Kind::kRelocatableWasmCall ||
           kind == Kind::kRelocatableWasmStubCall ||
           kind == Kind::kRelocatableWasmCanonicalSignatureId ||
           kind == Kind::kRelocatableWasmIndirectCallTarget ||
           kind == Kind::kRelocatableWasmCodePointer;
  }
};

// Load `loaded_rep` from: base + offset + index * 2^element_size_log2.
// For Kind::tagged_base: subtract kHeapObjectTag,
//                        `base` has to be the object start.
// For (u)int8/16, the value will be sign- or zero-extended to Word32.
// When result_rep is RegisterRepresentation::Compressed(), then the load does
// not decompress the value.
struct LoadOp : OperationT<LoadOp> {
  struct __attribute__((packed)) Kind {
    // The `base` input is a tagged pointer to a HeapObject.
    bool tagged_base : 1;
    // The effective address might be unaligned. This is only set to true if
    // the platform does not support unaligned loads for the given
    // MemoryRepresentation natively.
    bool maybe_unaligned : 1;
    // There is a Wasm trap handler for out-of-bounds accesses.
    bool with_trap_handler : 1;
    // The wasm trap handler is used for null accesses. Note that this requires
    // with_trap_handler as well.
    bool trap_on_null : 1;
    // If {load_eliminable} is true, then:
    //   - Stores/Loads at this address cannot overlap. Concretely, it means
    //     that something like this cannot happen:
    //
    //         const u32s = Uint32Array.of(3, 8);
    //         const u8s = new Uint8Array(u32s.buffer);
    //         u32s[0] = 0xffffffff;
    //         u8s[1] = 0; // Overlaps with previous store!
    //
    //   - Stores/Loads at this address have a canonical base. Concretely, it
    //     means that something like this cannot happen:
    //
    //         let buffer = new ArrayBuffer(10000);
    //         let ta1 = new Int32Array(buffer, 0/*offset*/);
    //         let ta2 = new Int32Array(buffer, 100*4/*offset*/);
    //         ta2[0] = 0xff;
    //         ta1[100] = 42; // Same destination as the previous store!
    //
    //   - No other thread can modify the underlying value. E.g. in the case of
    //     loading the wasm stack limit, other threads can modify the loaded
    //     value, so we always have to reload it.
    //
    // This is mainly used for load elimination: when stores/loads don't have
    // the {load_eliminable} bit set to true, more things need to be
    // invalidated.
    // In the main JS pipeline, only ArrayBuffers (= TypedArray/DataView)
    // loads/stores have this {load_eliminable} set to false,
    // and all other loads have it to true.
    bool load_eliminable : 1;
    // The loaded value may not change.
    bool is_immutable : 1;
    // The load should be atomic.
    bool is_atomic : 1;

    static constexpr Kind Aligned(BaseTaggedness base_is_tagged) {
      switch (base_is_tagged) {
        case BaseTaggedness::kTaggedBase:
          return TaggedBase();
        case BaseTaggedness::kUntaggedBase:
          return RawAligned();
      }
      UNREACHABLE();
    }
    static constexpr Kind TaggedBase() {
      return {.tagged_base = true,
              .maybe_unaligned = false,
              .with_trap_handler = false,
              .trap_on_null = false,
              .load_eliminable = true,
              .is_immutable = false,
              .is_atomic = false};
    }
    static constexpr Kind RawAligned() {
      return {.tagged_base = false,
              .maybe_unaligned = false,
              .with_trap_handler = false,
              .trap_on_null = false,
              .load_eliminable = true,
              .is_immutable = false,
              .is_atomic = false};
    }
    static constexpr Kind RawUnaligned() {
      return {.tagged_base = false,
              .maybe_unaligned = true,
              .with_trap_handler = false,
              .trap_on_null = false,
              .load_eliminable = true,
              .is_immutable = false,
              .is_atomic = false};
    }
    static constexpr Kind Trapping() {
      return {.tagged_base = false,
              .maybe_unaligned = false,
              .with_trap_handler = true,
              .trap_on_null = false,
              .load_eliminable = true,
              .is_immutable = false,
              .is_atomic = false};
    }
    static constexpr Kind TrapOnNull() {
      return {.tagged_base = true,
              .maybe_unaligned = false,
              .with_trap_handler = true,
              .trap_on_null = true,
              .load_eliminable = true,
              .is_immutable = false,
              .is_atomic = false};
    }
    static constexpr Kind MaybeUnaligned(MemoryRepresentation rep) {
      return rep == MemoryRepresentation::Int8() ||
                     rep == MemoryRepresentation::Uint8() ||
                     SupportedOperations::IsUnalignedLoadSupported(rep)
                 ? LoadOp::Kind::RawAligned()
                 : LoadOp::Kind::RawUnaligned();
    }

    [[nodiscard]] constexpr Kind NotLoadEliminable() {
      Kind new_kind = *this;
      new_kind.load_eliminable = false;
      return new_kind;
    }

    [[nodiscard]] constexpr Kind Immutable() const {
      Kind new_kind(*this);
      new_kind.is_immutable = true;
      return new_kind;
    }

    [[nodiscard]] constexpr Kind Atomic() const {
      Kind new_kind(*this);
      new_kind.is_atomic = true;
      return new_kind;
    }

    bool operator==(const Kind& other) const {
      return tagged_base == other.tagged_base &&
             maybe_unaligned == other.maybe_unaligned &&
             with_trap_handler == other.with_trap_handler &&
             load_eliminable == other.load_eliminable &&
             is_immutable == other.is_immutable &&
             is_atomic == other.is_atomic && trap_on_null == other.trap_on_null;
    }
  };
  Kind kind;
  MemoryRepresentation loaded_rep;
  RegisterRepresentation result_rep;
  uint8_t element_size_log2;  // multiply index with 2^element_size_log2
  int32_t offset;             // add offset to scaled index

  OpEffects Effects() const {
    // Loads might depend on checks for pointer validity, object layout, bounds
    // checks, etc.
    // TODO(tebbi): Distinguish between on-heap and off-heap loads.
    OpEffects effects = OpEffects().CanReadMemory().CanDependOnChecks();
    if (kind.with_trap_handler) effects = effects.CanLeaveCurrentFunction();
    if (kind.is_atomic) {
      // Atomic load should not be reordered with other loads.
      effects = effects.CanWriteMemory();
    }
    return effects;
  }
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&result_rep, 1);
  }

  MachineType machine_type() const;

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    base::Vector<const MaybeRegisterRepresentation> result =
        kind.tagged_base
            ? MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                             MaybeRegisterRepresentation::WordPtr()>()
            : MaybeRepVector<MaybeRegisterRepresentation::WordPtr(),
                             MaybeRegisterRepresentation::WordPtr()>();
    return index().valid() ? result : base::VectorOf(result.data(), 1);
  }

  OpIndex base() const { return input(0); }
  OptionalOpIndex index() const {
    return input_count == 2 ? input(1) : OpIndex::Invalid();
  }

  static constexpr bool OffsetIsValid(int32_t offset, bool tagged_base) {
    if (tagged_base) {
      // When a Load has the tagged_base Kind, it means that {offset} will
      // eventually need a "-kHeapObjectTag". If the {offset} is
      // min_int, then subtracting kHeapObjectTag will underflow.
      return offset >= std::numeric_limits<int32_t>::min() + kHeapObjectTag;
    }
    return true;
  }

  LoadOp(OpIndex base, OptionalOpIndex index, Kind kind,
         MemoryRepresentation loaded_rep, RegisterRepresentation result_rep,
         int32_t offset, uint8_t element_size_log2)
      : Base(1 + index.valid()),
        kind(kind),
        loaded_rep(loaded_rep),
        result_rep(result_rep),
        element_size_log2(element_size_log2),
        offset(offset) {
    input(0) = base;
    if (index.valid()) {
      input(1) = index.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(base()), mapper.Map(index()), kind, loaded_rep,
              result_rep, offset, element_size_log2);
  }

  void Validate(const Graph& graph) const {
    DCHECK(loaded_rep.ToRegisterRepresentation() == result_rep ||
           (loaded_rep.IsCompressibleTagged() &&
            result_rep == RegisterRepresentation::Compressed()) ||
           kind.is_atomic);
    DCHECK_IMPLIES(element_size_log2 > 0, index().valid());
    DCHECK_IMPLIES(kind.maybe_unaligned,
                   !SupportedOperations::IsUnalignedLoadSupported(loaded_rep));
    DCHECK(OffsetIsValid(offset, kind.tagged_base));
  }
  static LoadOp& New(Graph* graph, OpIndex base, OptionalOpIndex index,
                     Kind kind, MemoryRepresentation loaded_rep,
                     RegisterRepresentation result_rep, int32_t offset,
                     uint8_t element_size_log2) {
    return Base::New(graph, 1 + index.valid(), base, index, kind, loaded_rep,
                     result_rep, offset, element_size_log2);
  }
  void PrintInputs(std::ostream& os, const std::string& op_index_prefix) const;
  void PrintOptions(std::ostream& os) const;
  auto options() const {
    return std::tuple{kind, loaded_rep, result_rep, offset, element_size_log2};
  }
};

V8_INLINE size_t hash_value(LoadOp::Kind kind) {
  return base::hash_value(
      static_cast<int>(kind.tagged_base) | (kind.maybe_unaligned << 1) |
      (kind.load_eliminable << 2) | (kind.is_immutable << 3) |
      (kind.with_trap_handler << 4) | (kind.is_atomic << 5));
}

#if V8_ENABLE_SANDBOX
struct LoadTrustedPointerOp : FixedArityOperationT<2, LoadTrustedPointerOp> {
  LoadOp::Kind kind;
  IndirectPointerTagRange tag_range;
  const int offset;

  OpEffects Effects() const {
    // Loads might depend on checks for pointer validity, object layout, bounds
    // checks, etc.
    OpEffects effects = OpEffects().CanReadMemory().CanDependOnChecks();
    if (kind.with_trap_handler) effects = effects.CanLeaveCurrentFunction();
    return effects;
  }

  explicit LoadTrustedPointerOp(V<HeapObject> base, V<WordPtr> table,
                                LoadOp::Kind kind,
                                IndirectPointerTagRange tag_range, int offset)
      : Base(base, table), kind(kind), tag_range(tag_range), offset(offset) {
    DCHECK(kind.tagged_base);
    DCHECK(!kind.is_atomic);
    DCHECK(!kind.maybe_unaligned);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  V<HeapObject> base() const { return input<HeapObject>(0); }
  V<WordPtr> table() const { return input<WordPtr>(1); }

  void PrintOptions(std::ostream& os) const;
  auto options() const { return std::tuple{kind, tag_range, offset}; }
};
#endif

struct AtomicRMWOp : OperationT<AtomicRMWOp> {
  enum class BinOp : uint8_t {
    kAdd,
    kSub,
    kAnd,
    kOr,
    kXor,
    kExchange,
    kCompareExchange
  };
  BinOp bin_op;
  RegisterRepresentation in_out_rep;
  MemoryRepresentation memory_rep;
  MemoryAccessKind memory_access_kind;
  RegisterRepresentation base_rep;

  OpEffects Effects() const {
    OpEffects effects =
        OpEffects().CanWriteMemory().CanDependOnChecks().CanReadMemory();
    if (memory_access_kind == MemoryAccessKind::kTrapping) {
      effects = effects.CanLeaveCurrentFunction();
    }
    return effects;
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&in_out_rep, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    if (bin_op == BinOp::kCompareExchange) {
      return InitVectorOf(storage, {base_rep, RegisterRepresentation::WordPtr(),
                                    in_out_rep, in_out_rep});
    }
    return InitVectorOf(
        storage, {base_rep, RegisterRepresentation::WordPtr(), in_out_rep});
  }

  V<Any> base() const { return input<Any>(0); }
  // The offset from base. Note that for tagged bases this offset does include
  // the pointer untagging, differently to e.g. loads and stores!
  V<WordPtr> index() const { return input<WordPtr>(1); }
  OpIndex value() const { return input(2); }
  OptionalOpIndex expected() const {
    return (input_count == 4) ? input(3) : OpIndex::Invalid();
  }

  void Validate(const Graph& graph) const {
    DCHECK_EQ(bin_op == BinOp::kCompareExchange, expected().valid());
    DCHECK(base_rep == RegisterRepresentation::WordPtr() ||
           base_rep == RegisterRepresentation::Tagged());
  }

  AtomicRMWOp(OpIndex base, OpIndex index, OpIndex value,
              OptionalOpIndex expected, BinOp bin_op,
              RegisterRepresentation in_out_rep,
              MemoryRepresentation memory_rep, MemoryAccessKind kind,
              RegisterRepresentation base_rep)
      : Base(3 + expected.valid()),
        bin_op(bin_op),
        in_out_rep(in_out_rep),
        memory_rep(memory_rep),
        memory_access_kind(kind),
        base_rep(base_rep) {
    input(0) = base;
    input(1) = index;
    input(2) = value;
    if (expected.valid()) {
      input(3) = expected.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(base()), mapper.Map(index()), mapper.Map(value()),
              mapper.Map(expected()), bin_op, in_out_rep, memory_rep,
              memory_access_kind, base_rep);
  }

  static AtomicRMWOp& New(Graph* graph, OpIndex base, V<WordPtr> index,
                          OpIndex value, OptionalOpIndex expected, BinOp bin_op,
                          RegisterRepresentation result_rep,
                          MemoryRepresentation input_rep, MemoryAccessKind kind,
                          RegisterRepresentation base_rep) {
    return Base::New(graph, 3 + expected.valid(), base, index, value, expected,
                     bin_op, result_rep, input_rep, kind, base_rep);
  }

  void PrintInputs(std::ostream& os, const std::string& op_index_prefix) const;

  void PrintOptions(std::ostream& os) const;

  auto options() const {
    return std::tuple{bin_op, in_out_rep, memory_rep, memory_access_kind,
                      base_rep};
  }
};
DEFINE_MULTI_SWITCH_INTEGRAL(AtomicRMWOp::BinOp, 8)

V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           AtomicRMWOp::BinOp kind);

struct AtomicWord32PairOp : OperationT<AtomicWord32PairOp> {
  enum class Kind : uint8_t {
    kAdd,
    kSub,
    kAnd,
    kOr,
    kXor,
    kExchange,
    kCompareExchange,
    kLoad,
    kStore
  };

  Kind kind;
  int32_t offset;

  static Kind KindFromBinOp(AtomicRMWOp::BinOp bin_op) {
    switch (bin_op) {
      case AtomicRMWOp::BinOp::kAdd:
        return Kind::kAdd;
      case AtomicRMWOp::BinOp::kSub:
        return Kind::kSub;
      case AtomicRMWOp::BinOp::kAnd:
        return Kind::kAnd;
      case AtomicRMWOp::BinOp::kOr:
        return Kind::kOr;
      case AtomicRMWOp::BinOp::kXor:
        return Kind::kXor;
      case AtomicRMWOp::BinOp::kExchange:
        return Kind::kExchange;
      case AtomicRMWOp::BinOp::kCompareExchange:
        return Kind::kCompareExchange;
    }
    UNREACHABLE();
  }

  OpEffects Effects() const {
    OpEffects effects = OpEffects().CanDependOnChecks();
    if (kind == Kind::kStore) {
      return effects.CanWriteMemory();
    }
    // Atomic loads are marked as "can write memory" as they should not be
    // reordered with other loads. Secondly, they may not be removed even if
    // unused as they might make writes of other threads visible.
    return effects.CanReadMemory().CanWriteMemory();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    if (kind == Kind::kStore) return {};
    return RepVector<RegisterRepresentation::Word32(),
                     RegisterRepresentation::Word32()>();
  }
  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    storage.resize(input_count);

    const bool has_index = HasIndex();
    storage[0] = RegisterRepresentation::WordPtr();  // base
    if (has_index) {
      storage[1] = RegisterRepresentation::WordPtr();  // index
    }
    if (kind != Kind::kLoad) {
      storage[1 + has_index] = RegisterRepresentation::Word32();  // value_low
      storage[2 + has_index] = RegisterRepresentation::Word32();  // value_high
      if (kind == Kind::kCompareExchange) {
        storage[3 + has_index] =
            RegisterRepresentation::Word32();  // expected_low
        storage[4 + has_index] =
            RegisterRepresentation::Word32();  // expected_high
      }
    }
    return base::VectorOf(storage);
  }

  V<WordPtr> base() const { return input<WordPtr>(0); }
  OptionalV<WordPtr> index() const {
    return HasIndex() ? input<WordPtr>(1) : V<WordPtr>::Invalid();
  }
  OptionalV<Word32> value_low() const {
    return kind != Kind::kLoad ? input<Word32>(1 + HasIndex())
                               : V<Word32>::Invalid();
  }
  OptionalV<Word32> value_high() const {
    return kind != Kind::kLoad ? input<Word32>(2 + HasIndex())
                               : V<Word32>::Invalid();
  }
  OptionalV<Word32> expected_low() const {
    return kind == Kind::kCompareExchange ? input<Word32>(3 + HasIndex())
                                          : V<Word32>::Invalid();
  }
  OptionalV<Word32> expected_high() const {
    return kind == Kind::kCompareExchange ? input<Word32>(4 + HasIndex())
                                          : V<Word32>::Invalid();
  }


  AtomicWord32PairOp(V<WordPtr> base, OptionalV<WordPtr> index,
                     OptionalV<Word32> value_low, OptionalV<Word32> value_high,
                     OptionalV<Word32> expected_low,
                     OptionalV<Word32> expected_high, Kind kind, int32_t offset)
      : Base(InputCount(kind, index.has_value())), kind(kind), offset(offset) {
    DCHECK_EQ(value_low.valid(), value_high.valid());
    DCHECK_EQ(expected_low.valid(), expected_high.valid());
    DCHECK_EQ(kind == Kind::kCompareExchange, expected_low.valid());
    DCHECK_EQ(kind != Kind::kLoad, value_low.valid());

    const bool has_index = index.has_value();
    DCHECK_EQ(has_index, HasIndex());

    input(0) = base;
    if (has_index) input(1) = index.value();
    if (kind != Kind::kLoad) {
      input(1 + has_index) = value_low.value();
      input(2 + has_index) = value_high.value();
      if (kind == Kind::kCompareExchange) {
        input(3 + has_index) = expected_low.value();
        input(4 + has_index) = expected_high.value();
      }
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(base()), mapper.Map(index()), mapper.Map(value_low()),
              mapper.Map(value_high()), mapper.Map(expected_low()),
              mapper.Map(expected_high()), kind, offset);
  }

  static constexpr size_t InputCount(Kind kind, bool has_index) {
    switch (kind) {
      case Kind::kLoad:
        return 1 + has_index;  // base, index?
      case Kind::kAdd:
      case Kind::kSub:
      case Kind::kAnd:
      case Kind::kOr:
      case Kind::kXor:
      case Kind::kExchange:
      case Kind::kStore:
        return 3 + has_index;  // base, index?, value_low, value_high
      case Kind::kCompareExchange:
        return 5 + has_index;  // base, index?, value_low, value_high,
                               // expected_low, expected_high
    }
    UNREACHABLE();
  }
  bool HasIndex() const { return input_count == InputCount(kind, true); }

  static AtomicWord32PairOp& New(Graph* graph, V<WordPtr> base,
                                 OptionalV<WordPtr> index,
                                 OptionalV<Word32> value_low,
                                 OptionalV<Word32> value_high,
                                 OptionalV<Word32> expected_low,
                                 OptionalV<Word32> expected_high, Kind kind,
                                 int32_t offset) {
    return Base::New(graph, InputCount(kind, index.has_value()), base, index,
                     value_low, value_high, expected_low, expected_high, kind,
                     offset);
  }

  void PrintInputs(std::ostream& os, const std::string& op_index_prefix) const;

  void PrintOptions(std::ostream& os) const;

  auto options() const { return std::tuple{kind, offset}; }
};

V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           AtomicWord32PairOp::Kind kind);

#ifdef V8_ENABLE_SANDBOX_HARDWARE_SUPPORT
struct SwitchSandboxModeOp : FixedArityOperationT<0, SwitchSandboxModeOp> {
  CodeSandboxingMode sandbox_mode;

  static constexpr OpEffects effects =
      OpEffects().CanReadHeapMemory().CanWriteMemory();

  explicit SwitchSandboxModeOp(CodeSandboxingMode sandbox_mode)
      : Base(), sandbox_mode(sandbox_mode) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  auto options() const { return std::tuple{sandbox_mode}; }
  void PrintOptions(std::ostream& os) const;
};
#endif

struct MemoryBarrierOp : FixedArityOperationT<0, MemoryBarrierOp> {
  AtomicMemoryOrder memory_order;

  static constexpr OpEffects effects =
      OpEffects().CanReadHeapMemory().CanWriteMemory();

  explicit MemoryBarrierOp(AtomicMemoryOrder memory_order)
      : Base(), memory_order(memory_order) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }


  auto options() const { return std::tuple{memory_order}; }
  void PrintOptions(std::ostream& os) const;
};

// "Pause" operation used to indicate the thread is doing something of
// low-importance, e.g. a spinlock, see also the x86 pause instruction.
struct PauseOp : FixedArityOperationT<0, PauseOp> {
  // While this operation doesn't have any observable side effects, it should
  // not be optimized away (its whole purpose is to affect the generated code.)
  static constexpr OpEffects effects = OpEffects().RequiredWhenUnused();

  PauseOp() = default;

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  auto options() const { return std::tuple{}; }
};

// Store `value` to: base + offset + index * 2^element_size_log2.
// For Kind::tagged_base: subtract kHeapObjectTag,
//                        `base` has to be the object start.
struct StoreOp : OperationT<StoreOp> {
  using Kind = LoadOp::Kind;
  Kind kind;
  MemoryRepresentation stored_rep;
  WriteBarrierKind write_barrier;
  uint8_t element_size_log2;  // multiply index with 2^element_size_log2
  int32_t offset;             // add offset to scaled index
  bool maybe_initializing_or_transitioning;

 private:
  AtomicMemoryOrder memory_order_;  // use the padding after prev field

 public:
  uint16_t indirect_pointer_tag_;

  std::optional<AtomicMemoryOrder> memory_order() const {
    if (kind.is_atomic) return memory_order_;
    return std::nullopt;
  }

  // TODO(saelo): now that we have a pointer tag in these low-level operations,
  // we could also consider passing the external pointer tag (for external
  // pointers) through to the macro assembler (where we have routines to work
  // with external pointers) instead of handling those earlier in the compiler.
  // We might lose the ability to hardcode the table address though.

  OpEffects Effects() const {
    // Stores might depend on checks for pointer validity, object layout, bounds
    // checks, etc.
    // TODO(tebbi): Distinghish between on-heap and off-heap stores.
    OpEffects effects = OpEffects().CanWriteMemory().CanDependOnChecks();
    if (kind.with_trap_handler) effects = effects.CanLeaveCurrentFunction();
    if (maybe_initializing_or_transitioning) {
      effects = effects.CanDoRawHeapAccess();
    }
    if (kind.is_atomic) {
      // Atomic stores should not be eliminated away, even if the situation
      // seems to allow e.g. store-store elimination. Elimination is avoided by
      // setting the `CanReadMemory` effect.
      effects = effects.CanReadMemory();
    }
    return effects;
  }
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    RegisterRepresentation base = kind.tagged_base
                                      ? RegisterRepresentation::Tagged()
                                      : RegisterRepresentation::WordPtr();
    if (index() == OpIndex::Invalid()) {
      return InitVectorOf(
          storage, {base, stored_rep.ToRegisterRepresentationForStore()});
    }
    return InitVectorOf(storage,
                        {base, stored_rep.ToRegisterRepresentationForStore(),
                         RegisterRepresentation::WordPtr()});
  }

  OpIndex base() const { return input(0); }
  OpIndex value() const { return input(1); }
  OptionalOpIndex index() const {
    return input_count == 3 ? input(2) : OpIndex::Invalid();
  }

  IndirectPointerTag indirect_pointer_tag() const {
    return static_cast<IndirectPointerTag>(indirect_pointer_tag_);
  }

  StoreOp(
      OpIndex base, OptionalOpIndex index, OpIndex value, Kind kind,
      MemoryRepresentation stored_rep, WriteBarrierKind write_barrier,
      std::optional<AtomicMemoryOrder> memory_order, int32_t offset,
      uint8_t element_size_log2, bool maybe_initializing_or_transitioning,
      IndirectPointerTag maybe_indirect_pointer_tag = kIndirectPointerNullTag)
      : Base(2 + index.valid()),
        kind(kind),
        stored_rep(stored_rep),
        write_barrier(write_barrier),
        element_size_log2(element_size_log2),
        offset(offset),
        maybe_initializing_or_transitioning(
            maybe_initializing_or_transitioning),
        memory_order_(memory_order.value_or(AtomicMemoryOrder::kSeqCst)),
        indirect_pointer_tag_(
            static_cast<uint16_t>(maybe_indirect_pointer_tag)) {
    DCHECK_EQ(indirect_pointer_tag(), maybe_indirect_pointer_tag);
    input(0) = base;
    input(1) = value;
    if (index.valid()) {
      input(2) = index.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(base()), mapper.Map(index()), mapper.Map(value()),
              kind, stored_rep, write_barrier, memory_order(), offset,
              element_size_log2, maybe_initializing_or_transitioning,
              indirect_pointer_tag());
  }

  void Validate(const Graph& graph) const {
    DCHECK_IMPLIES(element_size_log2 > 0, index().valid());
    DCHECK_IMPLIES(kind.maybe_unaligned,
                   !SupportedOperations::IsUnalignedLoadSupported(stored_rep));
    DCHECK(LoadOp::OffsetIsValid(offset, kind.tagged_base));
    DCHECK_EQ(kind.is_atomic, memory_order().has_value());
  }
  static StoreOp& New(
      Graph* graph, OpIndex base, OptionalOpIndex index, OpIndex value,
      Kind kind, MemoryRepresentation stored_rep,
      WriteBarrierKind write_barrier,
      std::optional<AtomicMemoryOrder> memory_order, int32_t offset,
      uint8_t element_size_log2, bool maybe_initializing_or_transitioning,
      IndirectPointerTag maybe_indirect_pointer_tag = kIndirectPointerNullTag) {
    return Base::New(graph, 2 + index.valid(), base, index, value, kind,
                     stored_rep, write_barrier, memory_order, offset,
                     element_size_log2, maybe_initializing_or_transitioning,
                     maybe_indirect_pointer_tag);
  }

  void PrintInputs(std::ostream& os, const std::string& op_index_prefix) const;
  void PrintOptions(std::ostream& os) const;
  auto options() const {
    return std::tuple{kind,
                      stored_rep,
                      write_barrier,
                      memory_order(),
                      offset,
                      element_size_log2,
                      maybe_initializing_or_transitioning};
  }
};

struct AllocateOp : FixedArityOperationT<1, AllocateOp> {
  AllocationType type;
  AllocationAlignment alignment;

  static constexpr OpEffects effects =
      OpEffects()
          .CanAllocate()
          // The resulting object is unitialized, which leaves the heap in an
          // inconsistent state.
          .CanDoRawHeapAccess()
          // Do not move allocations before checks, to avoid OOM or invalid
          // size.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr()>();
  }

  V<WordPtr> size() const { return input<WordPtr>(0); }

  AllocateOp(V<WordPtr> size, AllocationType type,
             AllocationAlignment alignment)
      : Base(size), type(type), alignment(alignment) {}

  void PrintOptions(std::ostream& os) const;

  auto options() const { return std::tuple{type, alignment}; }

  void Validate(const Graph& graph) const {
    // Operations with different alignment than kTaggedAligned are currently
    // only supported for shared old space allocations from wasm.
    DCHECK_IMPLIES(alignment != kTaggedAligned,
                   type == AllocationType::kSharedOld);
  }
};

#if V8_ENABLE_SANDBOX
struct LoadExternalPointerOp : FixedArityOperationT<1, LoadExternalPointerOp> {
  ExternalPointerTagRange tag_range;

  // Accessing external pointers is only safe if the garbage collected pointer
  // keeping the external pointer alive is retained for the length of the
  // operation. For this, it is essential that we use a `Retain` operation
  // placed after the last access to the external data.
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::WordPtr()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> handle() const { return input<Word32>(0); }

  LoadExternalPointerOp(V<Word32> handle, ExternalPointerTagRange tag_range)
      : Base(handle), tag_range(tag_range) {}

  void Validate(const Graph& graph) const { DCHECK(!tag_range.IsEmpty()); }
  void PrintOptions(std::ostream& os) const;
  auto options() const { return std::tuple{tag_range}; }
};
#endif

struct JSStackCheckOp : OperationT<JSStackCheckOp> {
  enum class Kind : uint8_t { kFunctionEntry, kBuiltinEntry, kLoop };
  Kind kind;

  OpEffects Effects() const {
    switch (kind) {
      case Kind::kFunctionEntry:
        return OpEffects().CanCallAnything();
      case Kind::kBuiltinEntry:
        return OpEffects().CanCallAnything();
      case Kind::kLoop:
        // Loop body iteration stack checks can't write memory.
        // TODO(dmercadier): we could prevent this from allocating. In
        // particular, we'd need to:
        //   - forbid GC interrupts from being processed in loop stack checks.
        //   - make sure that the debugger always deopts the current function
        //     when it triggers a loop interrupt.
        return OpEffects()
            .CanDependOnChecks()
            .CanDeopt()
            .CanReadHeapMemory()
            .CanAllocate();
    }
    UNREACHABLE();
  }

  V<Context> native_context() const { return Base::input<Context>(0); }
  OptionalV<LazyFrameState> frame_state() const {
    return input_count > 1 ? Base::input<LazyFrameState>(1)
                           : OptionalV<LazyFrameState>::Nullopt();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit JSStackCheckOp(V<Context> context,
                          OptionalV<LazyFrameState> frame_state, Kind kind)
      : Base(1 + frame_state.has_value()), kind(kind) {
    input(0) = context;
    if (frame_state.has_value()) {
      input(1) = frame_state.value();
    }
  }

  static JSStackCheckOp& New(Graph* graph, V<Context> context,
                             OptionalV<LazyFrameState> frame_state, Kind kind) {
    return Base::New(graph, 1 + frame_state.has_value(), context, frame_state,
                     kind);
  }

  void Validate(const Graph& graph) const {
    DCHECK_EQ(kind == Kind::kBuiltinEntry, !frame_state().has_value());
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(native_context()), mapper.Map(frame_state()), kind);
  }

  auto options() const { return std::tuple{kind}; }
};

// Retain a HeapObject to prevent it from being garbage collected too early.
struct RetainOp : FixedArityOperationT<1, RetainOp> {
  V<Object> retained() const { return input<Object>(0); }

  // Retain doesn't actually write, it just keeps a value alive. However, since
  // this must not be reordered with reading operations, we mark it as writing.
  static constexpr OpEffects effects = OpEffects().CanWriteMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  explicit RetainOp(V<Object> retained) : Base(retained) {}

  auto options() const { return std::tuple{}; }
};

// We compare the stack pointer register with the given limit and a
// codegen-dependant adjustment.
struct StackPointerGreaterThanOp
    : FixedArityOperationT<1, StackPointerGreaterThanOp> {
  StackCheckKind kind;

  // Since the frame size of optimized functions is constant, this behaves like
  // a pure operation.
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr()>();
  }

  V<WordPtr> stack_limit() const { return input<WordPtr>(0); }

  StackPointerGreaterThanOp(V<WordPtr> stack_limit, StackCheckKind kind)
      : Base(stack_limit), kind(kind) {}

  auto options() const { return std::tuple{kind}; }
};

// Allocate a piece of memory in the current stack frame. Every operation
// in the IR is a separate stack slot, but repeated execution in a loop
// produces the same stack slot.
struct StackSlotOp : FixedArityOperationT<0, StackSlotOp> {
  int size;
  int alignment;
  bool is_tagged;

  // We can freely reorder stack slot operations, but must not de-duplicate
  // them.
  static constexpr OpEffects effects = OpEffects().CanCreateIdentity();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::WordPtr()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  StackSlotOp(int size, int alignment, bool is_tagged = false)
      : size(size), alignment(alignment), is_tagged(is_tagged) {}
  auto options() const { return std::tuple{size, alignment, is_tagged}; }
};

// Values that are constant for the current stack frame/invocation.
// Therefore, they behaves like a constant, even though they are different for
// every invocation.
struct FrameConstantOp : FixedArityOperationT<0, FrameConstantOp> {
  enum class Kind : uint8_t {
    kStackCheckOffset,
    kFramePointer,
    kParentFramePointer
  };
  Kind kind;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (kind) {
      case Kind::kStackCheckOffset:
        return RepVector<RegisterRepresentation::Tagged()>();
      case Kind::kFramePointer:
      case Kind::kParentFramePointer:
        return RepVector<RegisterRepresentation::WordPtr()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit FrameConstantOp(Kind kind) : Base(), kind(kind) {}
  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           FrameConstantOp::Kind kind);

struct FrameStateOp : OperationT<FrameStateOp> {
  bool inlined;
  const FrameStateData* data;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  V<AnyFrameState> parent_frame_state() const {
    DCHECK(inlined);
    // Bypasses input<AnyFrameState>() using a manual cast to avoid triggering
    // the static assertion in OperationT against AnyFrameState inputs.
    return V<AnyFrameState>::Cast(inputs()[0]);
  }
  base::Vector<const OpIndex> state_values() const {
    base::Vector<const OpIndex> result = inputs();
    if (inlined) result += 1;
    return result;
  }
  uint16_t state_values_count() const {
    DCHECK_EQ(input_count - inlined, state_values().size());
    return input_count - inlined;
  }
  const OpIndex state_value(size_t idx) const { return state_values()[idx]; }

  RegisterRepresentation state_value_rep(size_t idx) const {
    return RegisterRepresentation::FromMachineRepresentation(
        data->machine_types[idx].representation());
  }

  FrameStateOp(base::Vector<const OpIndex> inputs, bool inlined,
               const FrameStateData* data)
      : Base(inputs), inlined(inlined), data(data) {}

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    auto mapped_inputs = mapper.template Map<32>(inputs());
    return fn(base::VectorOf(mapped_inputs), inlined, data);
  }

  size_t hash_value(
      HashingStrategy strategy = HashingStrategy::kDefault) const {
    // Computing the full hash of {data} would be a bit slow, so we just use the
    // `bailout_id` and `inlined` (+ the inputs) to get a quick hash (and rely
    // on the == operator to properly check later if 2 FrameStateOp with the
    // same hash are really equal)
    return HashWithOptions(inlined, data->frame_state_info.bailout_id());
  }

  V8_EXPORT_PRIVATE void Validate(const Graph& graph) const;
  V8_EXPORT_PRIVATE void PrintOptions(std::ostream& os) const;
  auto options() const { return std::tuple{inlined, *data}; }
};

struct DeoptimizeOp : FixedArityOperationT<1, DeoptimizeOp> {
  const DeoptimizeParameters* parameters;

  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  V<EagerFrameState> frame_state() const { return input<EagerFrameState>(0); }

  DeoptimizeOp(V<EagerFrameState> frame_state,
               const DeoptimizeParameters* parameters)
      : Base(frame_state), parameters(parameters) {}
  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }
  auto options() const { return std::tuple{parameters}; }
};

struct DeoptimizeIfOp : FixedArityOperationT<2, DeoptimizeIfOp> {
  bool negated;
  const DeoptimizeParameters* parameters;

  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> condition() const { return input<Word32>(0); }
  V<EagerFrameState> frame_state() const { return input<EagerFrameState>(1); }

  DeoptimizeIfOp(V<Word32> condition, V<EagerFrameState> frame_state,
                 bool negated, const DeoptimizeParameters* parameters)
      : Base(condition, frame_state),
        negated(negated),
        parameters(parameters) {}

  bool EqualsForGVN(const DeoptimizeIfOp& other) const {
    // As far as GVN is concerned, the `frame_state` and `parameters` don't
    // matter: 2 DeoptimizeIf can be GVNed if they have the same `condition` and
    // same `negated`, regardless of their `frame_state` and `parameters`.
    return condition() == other.condition() && negated == other.negated;
  }
  size_t hash_value(
      HashingStrategy strategy = HashingStrategy::kDefault) const {
    // To enable GVNing as described above in `EqualsForGVN`, `hash_value` has
    // to ignore the `frame_state` and the `parameters`.
    return fast_hash_combine(Opcode::kDeoptimizeIf, condition(), negated);
  }
  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }
  auto options() const { return std::tuple{negated, parameters}; }
  void PrintOptions(std::ostream& os) const;
};
struct PrepareForLoopOp : FixedArityOperationT<1, PrepareForLoopOp> {
  FeedbackSource feedback;

  static constexpr OpEffects effects = OpEffects().RequiredWhenUnused();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  V<EagerFrameState> frame_state() const { return input<EagerFrameState>(0); }

  PrepareForLoopOp(V<EagerFrameState> frame_state, FeedbackSource feedback)
      : Base(frame_state), feedback(feedback) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }
  auto options() const { return std::tuple{feedback}; }
};

#if V8_ENABLE_WEBASSEMBLY

// A WebAssembly stack check operation.
// If input_count > 0:
// - input(0) is trusted_instance_data
struct WasmStackCheckOp : OperationT<WasmStackCheckOp> {
  using Kind = JSStackCheckOp::Kind;
  Kind kind;

  OpEffects Effects() const {
    switch (kind) {
      case Kind::kLoop:
        // A loop stack check can have arbitrary side effects via debugger
        // interrupt requests; in particular it can trigger memory growth.
        return OpEffects()
            .RequiredWhenUnused()
            .CanReadMemory()
            .CanWriteMemory()
            .CanAllocate();
      case Kind::kFunctionEntry:
        // For function entry stack checks, we could model their side effects
        // somewhat more accurately, but it probably doesn't matter.
        return OpEffects().CanCallAnything();
      case Kind::kBuiltinEntry:
        UNREACHABLE();
    }
    UNREACHABLE();
  }

  OptionalV<WasmTrustedInstanceData> trusted_instance_data() const {
    return input_count > 0 ? input<WasmTrustedInstanceData>(0)
                           : V<WasmTrustedInstanceData>::Invalid();
  }

  WasmStackCheckOp(OptionalV<WasmTrustedInstanceData> trusted_instance_data,
                   Kind kind)
      : Base(trusted_instance_data.valid() ? 1 : 0), kind(kind) {
    if (trusted_instance_data.valid()) {
      input(0) = trusted_instance_data.value();
    }
  }

  static WasmStackCheckOp& New(
      Graph* graph, OptionalV<WasmTrustedInstanceData> trusted_instance_data,
      Kind kind) {
    size_t input_count = trusted_instance_data.valid() ? 1 : 0;
    return Base::New(graph, input_count, trusted_instance_data, kind);
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(trusted_instance_data()), kind);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    if (input_count == 0) {
      return {};
    }
    storage.resize(input_count);
    storage[0] = MaybeRegisterRepresentation::Tagged();
    return base::VectorOf(storage);
  }

  void Validate(const Graph& graph) const {
    if (kind == Kind::kFunctionEntry) {
      DCHECK_EQ(input_count, 0);
      DCHECK(!trusted_instance_data().valid());
    } else if (kind == Kind::kLoop) {
      DCHECK_LE(input_count, 1);
      if (input_count > 0) {
        DCHECK(trusted_instance_data().valid());
      }
    } else {
      UNREACHABLE();
    }
  }

  auto options() const { return std::tuple{kind}; }
};

V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           WasmStackCheckOp::Kind kind);
struct TrapIfOp : OperationT<TrapIfOp> {
  bool negated;
  const TrapId trap_id;

  static constexpr OpEffects effects =
      OpEffects()
          // Traps must not float above a protective check.
          .CanDependOnChecks()
          // Subsequent code can rely on the trap not having happened.
          .CanLeaveCurrentFunction();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> condition() const { return input<Word32>(0); }
  OptionalV<EagerFrameState> frame_state() const {
    return input_count > 1 ? input<EagerFrameState>(1)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  TrapIfOp(V<Word32> condition, OptionalV<EagerFrameState> frame_state,
           bool negated, const TrapId trap_id)
      : Base(1 + frame_state.valid()), negated(negated), trap_id(trap_id) {
    input(0) = condition;
    if (frame_state.valid()) {
      input(1) = frame_state.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(condition()), mapper.Map(frame_state()), negated,
              trap_id);
  }

  static TrapIfOp& New(Graph* graph, V<Word32> condition,
                       OptionalV<EagerFrameState> frame_state, bool negated,
                       const TrapId trap_id) {
    return Base::New(graph, 1 + frame_state.valid(), condition, frame_state,
                     negated, trap_id);
  }

  void Validate(const Graph& graph) const {
    if (frame_state().valid()) {
      DCHECK(Get(graph, frame_state().value()).Is<FrameStateOp>());
    }
  }
  auto options() const { return std::tuple{negated, trap_id}; }
};

struct WasmTrapOp : OperationT<WasmTrapOp> {
  const TrapId trap_id;

  static constexpr OpEffects effects =
      OpEffects()
          // Traps must not float above a protective check.
          .CanDependOnChecks()
          // This will always leave the current function. (It will always trap
          // and cannot be caught in Wasm.)
          .CanLeaveCurrentFunction();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  OptionalV<EagerFrameState> frame_state() const {
    return input_count > 0 ? input<EagerFrameState>(0)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  WasmTrapOp(OptionalV<EagerFrameState> frame_state, const TrapId trap_id)
      : Base(frame_state.valid() ? 1 : 0), trap_id(trap_id) {
    if (frame_state.valid()) {
      input(0) = frame_state.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(frame_state()), trap_id);
  }

  static WasmTrapOp& New(Graph* graph, OptionalV<EagerFrameState> frame_state,
                         const TrapId trap_id) {
    return Base::New(graph, frame_state.valid() ? 1 : 0, frame_state, trap_id);
  }

  void Validate(const Graph& graph) const {
    if (frame_state().valid()) {
      DCHECK(Get(graph, frame_state().value()).Is<FrameStateOp>());
    }
  }
  auto options() const { return std::tuple{trap_id}; }
};

struct MemoryCopyOp : FixedArityOperationT<3, MemoryCopyOp> {
  // Depends on one or more bounds checks.
  static constexpr OpEffects effects =
      OpEffects().CanReadMemory().CanWriteMemory().CanDependOnChecks();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  MemoryCopyOp(OpIndex dst_base, OpIndex src_base, OpIndex num_bytes)
      : Base(dst_base, src_base, num_bytes) {}

  std::tuple<> options() const { return {}; }

  V<WordPtr> dst_base() const { return input<WordPtr>(0); }
  V<WordPtr> src_base() const { return input<WordPtr>(1); }
  V<WordPtr> num_bytes() const { return input<WordPtr>(2); }
};

struct MemoryFillOp : FixedArityOperationT<3, MemoryFillOp> {
  // Depends on one or more bounds checks.
  static constexpr OpEffects effects =
      OpEffects().CanWriteMemory().CanDependOnChecks();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  MemoryFillOp(V<WordPtr> dst_base, V<Word32> value, V<WordPtr> num_bytes)
      : Base(dst_base, value, num_bytes) {}

  std::tuple<> options() const { return {}; }

  V<WordPtr> dst_base() const { return input<WordPtr>(0); }
  V<Word32> value() const { return input<Word32>(1); }
  V<WordPtr> num_bytes() const { return input<WordPtr>(2); }
};

// Materialize the arg buffer passed from the suspend instruction to the
// target effect handler, similar to how we materialize the exception or
// continuation object in CatchBlockBeginOp.
struct WasmFXArgBufferOp : FixedArityOperationT<0, WasmFXArgBufferOp> {
  // This op should always appear at the start of a wasm effect handler block,
  // when the fixed register has not been clobbered yet.
  // Conservatively assume arbitrary side effects to prevent reordering.
  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::WordPtr()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  WasmFXArgBufferOp() : Base() {}
  auto options() const { return std::tuple{}; }
};
#endif  // V8_ENABLE_WEBASSEMBLY

struct MajorGCForCompilerTestingOp
    : FixedArityOperationT<0, MajorGCForCompilerTestingOp> {
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().RequiredWhenUnused().CanAllocate();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<>();
  }

  MajorGCForCompilerTestingOp() : Base() {}

  auto options() const { return std::tuple{}; }
};

struct StaticAssertOp : FixedArityOperationT<1, StaticAssertOp> {
  const char* source;
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().RequiredWhenUnused();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> condition() const { return Base::input<Word32>(0); }

  StaticAssertOp(V<Word32> condition, const char* source)
      : Base(condition), source(source) {}

  auto options() const { return std::tuple{source}; }
};

struct ParameterOp : FixedArityOperationT<0, ParameterOp> {
  int32_t parameter_index;
  RegisterRepresentation rep;
  const char* debug_name;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return {&rep, 1};
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};  // On the callee side a parameter doesn't have an input.
  }

  explicit ParameterOp(int32_t parameter_index, RegisterRepresentation rep,
                       const char* debug_name = "")
      : Base(),
        parameter_index(parameter_index),
        rep(rep),
        debug_name(debug_name) {}
  auto options() const { return std::tuple{parameter_index, rep, debug_name}; }
  void PrintOptions(std::ostream& os) const;
};

struct OsrValueOp : FixedArityOperationT<0, OsrValueOp> {
  int32_t index;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit OsrValueOp(int32_t index) : Base(), index(index) {}
  auto options() const { return std::tuple{index}; }
};

struct TSCallDescriptor : public NON_EXPORTED_BASE(ZoneObject) {
  const CallDescriptor* descriptor;
  base::Vector<const RegisterRepresentation> in_reps;
  base::Vector<const RegisterRepresentation> out_reps;
  CanThrow can_throw;
  LazyDeoptOnThrow lazy_deopt_on_throw;
  // TODO(dlehmann,353475584): Since the `JSWasmCallParameters` are specific to
  // one particular call site, this assumes that (only works correctly if)
  // `TSCallDescriptor`s are not shared across different calls (which they are
  // not at the moment).
  // For sharing call descriptors, the `JSWasmCallParameters` need to be moved
  // to the CallOp, which causes a lot of code churn (needs touching all
  // `REDUCE(Call)`).
  const JSWasmCallParameters* js_wasm_call_parameters;

  TSCallDescriptor(const CallDescriptor* descriptor,
                   base::Vector<const RegisterRepresentation> in_reps,
                   base::Vector<const RegisterRepresentation> out_reps,
                   CanThrow can_throw, LazyDeoptOnThrow lazy_deopt_on_throw,
                   const JSWasmCallParameters* js_wasm_call_parameters)
      : descriptor(descriptor),
        in_reps(in_reps),
        out_reps(out_reps),
        can_throw(can_throw),
        lazy_deopt_on_throw(lazy_deopt_on_throw),
        js_wasm_call_parameters(js_wasm_call_parameters) {}

  static const TSCallDescriptor* Create(
      const CallDescriptor* descriptor, CanThrow can_throw,
      LazyDeoptOnThrow lazy_deopt_on_throw, Zone* graph_zone,
      const JSWasmCallParameters* js_wasm_call_parameters = nullptr) {
    DCHECK_IMPLIES(!can_throw, !lazy_deopt_on_throw);
    base::Vector<RegisterRepresentation> in_reps =
        graph_zone->AllocateVector<RegisterRepresentation>(
            descriptor->ParameterCount());
    for (size_t i = 0; i < descriptor->ParameterCount(); ++i) {
      in_reps[i] = RegisterRepresentation::FromMachineRepresentation(
          descriptor->GetParameterType(i).representation());
    }
    base::Vector<RegisterRepresentation> out_reps =
        graph_zone->AllocateVector<RegisterRepresentation>(
            descriptor->ReturnCount());
    for (size_t i = 0; i < descriptor->ReturnCount(); ++i) {
      out_reps[i] = RegisterRepresentation::FromMachineRepresentation(
          descriptor->GetReturnType(i).representation());
    }
    return graph_zone->New<TSCallDescriptor>(descriptor, in_reps, out_reps,
                                             can_throw, lazy_deopt_on_throw,
                                             js_wasm_call_parameters);
  }
};

template <>
struct fast_hash<TSCallDescriptor> {
  size_t operator()(const TSCallDescriptor& v) {
    const CallDescriptor& d = *v.descriptor;
    // This does not include all fields of the call descriptor, but it should be
    // sufficient to differentiate between different calls (and collisions are
    // not too critical).
    return fast_hash_combine(d.kind(), d.tag(), d.ReturnCount(),
                             d.ParameterCount(), d.GPParameterCount(),
                             d.FPParameterCount(), d.ParameterSlotCount(),
                             d.ReturnSlotCount(), d.flags());
  }
};

// If {target} is a HeapObject representing a builtin, return that builtin's ID.
V8_EXPORT_PRIVATE std::optional<Builtin> TryGetBuiltinId(
    const ConstantOp* target, JSHeapBroker* broker);

struct CallOp : OperationT<CallOp> {
  const TSCallDescriptor* descriptor;
  OpEffects callee_effects;

  OpEffects Effects() const { return callee_effects; }

  // The outputs are produced by the `DidntThrow` operation.
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }
  base::Vector<const RegisterRepresentation> results_rep() const {
    return descriptor->out_reps;
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    storage.resize(input_count);
    size_t i = 0;
    if (descriptor->descriptor->IsCodeObjectCall() ||
        descriptor->descriptor->IsJSFunctionCall() ||
        descriptor->descriptor->IsBuiltinPointerCall()) {
      storage[i++] = MaybeRegisterRepresentation::Tagged();
#ifdef V8_ENABLE_WEBASSEMBLY
    } else if (descriptor->descriptor->IsIndirectWasmFunctionCall()) {
      storage[i++] = MaybeRegisterRepresentation::Word32();
#endif
    } else {
      storage[i++] = MaybeRegisterRepresentation::WordPtr();
    }
    if (HasFrameState()) {
      storage[i++] = MaybeRegisterRepresentation::None();
    }
    for (auto rep : descriptor->in_reps) {
      // In JavaScript, parameters are optional.
      if (i >= input_count) break;
      storage[i++] = rep;
    }
    storage.resize(i);
    return base::VectorOf(storage);
  }

  bool HasFrameState() const {
    return descriptor->descriptor->NeedsFrameState();
  }

  V<CallTarget> callee() const { return input<CallTarget>(0); }
  OptionalV<LazyFrameState> frame_state() const {
    return HasFrameState() ? input<LazyFrameState>(1)
                           : OptionalV<LazyFrameState>::Nullopt();
  }
  base::Vector<const OpIndex> arguments() const {
    return inputs().SubVector(1 + HasFrameState(), input_count);
  }

  CallOp(V<CallTarget> callee, OptionalV<LazyFrameState> frame_state,
         base::Vector<const OpIndex> arguments,
         const TSCallDescriptor* descriptor, OpEffects effects)
      : Base(1 + frame_state.valid() + arguments.size()),
        descriptor(descriptor),
        callee_effects(effects) {
    base::Vector<OpIndex> inputs = this->inputs();
    inputs[0] = callee;
    if (frame_state.valid()) {
      inputs[1] = frame_state.value();
    }
    inputs.SubVector(1 + frame_state.valid(), inputs.size())
        .OverwriteWith(arguments);
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    V<CallTarget> mapped_callee = mapper.Map(callee());
    OptionalV<LazyFrameState> mapped_frame_state = mapper.Map(frame_state());
    auto mapped_arguments = mapper.template Map<16>(arguments());
    return fn(mapped_callee, mapped_frame_state,
              base::VectorOf(mapped_arguments), descriptor, Effects());
  }

  V8_EXPORT_PRIVATE void Validate(const Graph& graph) const;

  static CallOp& New(Graph* graph, V<CallTarget> callee,
                     OptionalV<LazyFrameState> frame_state,
                     base::Vector<const OpIndex> arguments,
                     const TSCallDescriptor* descriptor, OpEffects effects) {
    return Base::New(graph, 1 + frame_state.valid() + arguments.size(), callee,
                     frame_state, arguments, descriptor, effects);
  }
  // TODO(mliedtke): Should the hash function be overwritten, so that calls (and
  // potentially tail calls) can participate in GVN? Right now this is prevented
  // by every call descriptor being a different pointer.
  auto options() const { return std::tuple{descriptor, callee_effects}; }
  size_t hash_value(HashingStrategy strategy = HashingStrategy::kDefault) const;
  void PrintOptions(std::ostream& os) const;
};

struct EffectHandler {
  wasm::EffectHandlerTagIndex tag_and_kind;
  Block* block;
#if V8_ENABLE_WEBASSEMBLY
  // TODO(thibaudm): Move the whole struct behind the guard.
  wasm::CanonicalTypeIndex sig;
#endif

  bool is_switch() const { return tag_and_kind.is_switch(); }
  uint32_t tag_index() const { return tag_and_kind.index(); }
};

// Catch an exception from the first operation of the `successor` block and
// continue execution in `catch_block` in this case.
struct CheckExceptionOp : FixedArityOperationT<1, CheckExceptionOp> {
  Block* didnt_throw_block;
  Block* catch_block;
  // For WasmFX: an optional effect handler.
  // A CheckExceptionOp is inserted if the operation must handle either
  // exceptions, effects, or both. So users of this op must handle the case
  // where {catch_block} is null or {effect_handler} is empty.
  base::Vector<EffectHandler> effect_handlers;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  V<Any> throwing_operation() const { return input<Any>(0); }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  CheckExceptionOp(V<Any> throwing_operation, Block* successor,
                   Block* catch_block,
                   base::Vector<EffectHandler> effect_handlers = {})
      : Base(throwing_operation),
        didnt_throw_block(successor),
        catch_block(catch_block),
        effect_handlers(effect_handlers) {}

  V8_EXPORT_PRIVATE void Validate(const Graph& graph) const;

  size_t hash_value(HashingStrategy strategy = HashingStrategy::kDefault) const;
  auto options() const {
    return std::tuple{didnt_throw_block, catch_block, effect_handlers};
  }
};

// This is a pseudo-operation that marks the beginning of a catch block. It
// returns the caught exception.
struct CatchBlockBeginOp : FixedArityOperationT<0, CatchBlockBeginOp> {
  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  CatchBlockBeginOp() : Base() {}
  auto options() const { return std::tuple{}; }
};

// Throwing operations always appear together with `DidntThrowOp`, which
// produces the value in case that no exception was thrown. If the callsite is
// non-catching, then `DidntThrowOp` follows right after the throwing operation:
//
//   100: Call(...)
//   101: DidntThrow(100)
//   102: Foo(101)
//
// If the callsite can catch, then the
// pattern is as follows:
//
//   100: Call(...)
//   101: CheckException(B10, B11)
//
//   B10:
//   102: DidntThrow(100)
//   103: Foo(102)
//
//   B11:
//   200: CatchBlockBegin()
//   201: ...
//
// This complexity is mostly hidden from graph creation, with
// `DidntThrowOp` and `CheckExceptionOp` being inserted automatically.
// The correct way to produce `CheckExceptionOp` is to create an
// `Assembler::CatchScope`, which will cause all throwing operations
// to add a `CheckExceptionOp` automatically while the scope is active.
// Since `CopyingPhase` does this automatically, lowering throwing
// operations into an arbitrary subgraph works automatically.
struct DidntThrowOp : FixedArityOperationT<1, DidntThrowOp> {
  OpEffects throwing_op_effects;

  // If there is a `CheckException` operation with a catch block for
  // `throwing_operation`.
  bool has_catch_block;
  // This is a pointer to a vector instead of a vector to save a bit of memory,
  // using optimal 16 bytes instead of 24.
  const base::Vector<const RegisterRepresentation>* results_rep;

  OpEffects Effects() const { return throwing_op_effects; }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return *results_rep;
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::None()>();
  }

  OpIndex throwing_operation() const { return input(0); }

  explicit DidntThrowOp(
      OpIndex throwing_operation, bool has_catch_block,
      const base::Vector<const RegisterRepresentation>* results_rep,
      OpEffects throwing_op_effects)
      : Base(throwing_operation),
        throwing_op_effects(throwing_op_effects),
        has_catch_block(has_catch_block),
        results_rep(results_rep) {}
  V8_EXPORT_PRIVATE void Validate(const Graph& graph) const;
  auto options() const {
    return std::tuple{throwing_op_effects, has_catch_block};
  }
};

struct TailCallOp : OperationT<TailCallOp> {
  const TSCallDescriptor* descriptor;

  static constexpr OpEffects effects = OpEffects().CanLeaveCurrentFunction();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    // While TailCalls do return some values, those values are returned to the
    // caller rather than to the current function (and a TailCallOp thus never
    // has any uses), so we set the outputs_rep to empty. If you need to know
    // what a TailCallOp returns, you can find out in `descriptor->outputs_rep`.
    return {};
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    storage.resize(input_count);
    size_t i = 0;
#ifdef V8_ENABLE_WEBASSEMBLY
    if (descriptor->descriptor->IsIndirectWasmFunctionCall()) {
      storage[i++] = MaybeRegisterRepresentation::Word32();
    } else if (descriptor->descriptor->IsDirectWasmFunctionCall() ||
               descriptor->descriptor->IsWasmImportWrapper() ||
               descriptor->descriptor->IsWasmCapiFunction()) {
      storage[i++] = MaybeRegisterRepresentation::WordPtr();
    } else {
      storage[i++] = MaybeRegisterRepresentation::Tagged();
    }
#else
    storage[i++] = MaybeRegisterRepresentation::Tagged();
#endif
    for (auto rep : descriptor->in_reps) {
      storage[i++] = rep;
    }
    storage.resize(i);
    return base::VectorOf(storage);
  }

  OpIndex callee() const { return input(0); }
  base::Vector<const OpIndex> arguments() const {
    return inputs().SubVector(1, input_count);
  }

  TailCallOp(OpIndex callee, base::Vector<const OpIndex> arguments,
             const TSCallDescriptor* descriptor)
      : Base(1 + arguments.size()), descriptor(descriptor) {
    base::Vector<OpIndex> inputs = this->inputs();
    inputs[0] = callee;
    inputs.SubVector(1, inputs.size()).OverwriteWith(arguments);
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    OpIndex mapped_callee = mapper.Map(callee());
    auto mapped_arguments = mapper.template Map<16>(arguments());
    return fn(mapped_callee, base::VectorOf(mapped_arguments), descriptor);
  }

  static TailCallOp& New(Graph* graph, OpIndex callee,
                         base::Vector<const OpIndex> arguments,
                         const TSCallDescriptor* descriptor) {
    return Base::New(graph, 1 + arguments.size(), callee, arguments,
                     descriptor);
  }
  auto options() const { return std::tuple{descriptor}; }
  void PrintOptions(std::ostream& os) const;
};

// Control-flow should never reach here.
struct UnreachableOp : FixedArityOperationT<0, UnreachableOp> {
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanLeaveCurrentFunction();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  UnreachableOp() : Base() {}
  auto options() const { return std::tuple{}; }
};

struct ReturnOp : OperationT<ReturnOp> {
  // spill_caller_frame_slots signals that all caller stack located return
  // values should be spilled before reaching the InstructionSelector.
  // The growable stacks implementation does extra work to spill these values
  // and it cannot be performed during InstructionSelector lowering efficiently.
  bool spill_caller_frame_slots;
  static constexpr OpEffects effects = OpEffects().CanLeaveCurrentFunction();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    // TODO(mliedtke): Ideally, a return op would expect to get the correct
    // types for all its return values, not just the pop count.
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  // Number of additional stack slots to be removed.
  V<Word32> pop_count() const { return input<Word32>(0); }

  base::Vector<const OpIndex> return_values() const {
    return inputs().SubVector(1, input_count);
  }

  ReturnOp(V<Word32> pop_count, base::Vector<const OpIndex> return_values,
           bool spill_caller_frame_slots)
      : Base(1 + return_values.size()),
        spill_caller_frame_slots(spill_caller_frame_slots) {
    base::Vector<OpIndex> inputs = this->inputs();
    inputs[0] = pop_count;
    inputs.SubVector(1, inputs.size()).OverwriteWith(return_values);
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    OpIndex mapped_pop_count = mapper.Map(pop_count());
    auto mapped_return_values = mapper.template Map<4>(return_values());
    return fn(mapped_pop_count, base::VectorOf(mapped_return_values),
              spill_caller_frame_slots);
  }

  static ReturnOp& New(Graph* graph, V<Word32> pop_count,
                       base::Vector<const OpIndex> return_values,
                       bool spill_caller_frame_slots) {
    return Base::New(graph, 1 + return_values.size(), pop_count, return_values,
                     spill_caller_frame_slots);
  }
  auto options() const { return std::tuple{spill_caller_frame_slots}; }
};

struct GotoOp : FixedArityOperationT<0, GotoOp> {
  bool is_backedge;
  Block* destination;

  static constexpr OpEffects effects = OpEffects().CanChangeControlFlow();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit GotoOp(Block* destination, bool is_backedge)
      : Base(), is_backedge(is_backedge), destination(destination) {}
  size_t hash_value(HashingStrategy strategy = HashingStrategy::kDefault) const;
  auto options() const { return std::tuple{destination, is_backedge}; }
};

struct BranchOp : FixedArityOperationT<1, BranchOp> {
  BranchHint hint;
  Block* if_true;
  Block* if_false;

  static constexpr OpEffects effects = OpEffects().CanChangeControlFlow();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> condition() const { return input<Word32>(0); }

  BranchOp(V<Word32> condition, Block* if_true, Block* if_false,
           BranchHint hint)
      : Base(condition), hint(hint), if_true(if_true), if_false(if_false) {}

  size_t hash_value(HashingStrategy strategy = HashingStrategy::kDefault) const;
  auto options() const { return std::tuple{if_true, if_false, hint}; }
};

struct SwitchOp : FixedArityOperationT<1, SwitchOp> {
  struct Case {
    BranchHint hint;
    int32_t value;
    Block* destination;

    Case(int32_t value, Block* destination, BranchHint hint)
        : hint(hint), value(value), destination(destination) {}

    bool operator==(const Case& other) const {
      return value == other.value && destination == other.destination &&
             hint == other.hint;
    }
  };
  BranchHint default_hint;
  base::Vector<Case> cases;
  Block* default_case;

  static constexpr OpEffects effects = OpEffects().CanChangeControlFlow();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32()>();
  }

  V<Word32> input() const { return Base::input<Word32>(0); }

  SwitchOp(V<Word32> input, base::Vector<Case> cases, Block* default_case,
           BranchHint default_hint)
      : Base(input),
        default_hint(default_hint),
        cases(cases),
        default_case(default_case) {}

  V8_EXPORT_PRIVATE void Validate(const Graph& graph) const;
  void PrintOptions(std::ostream& os) const;
  size_t hash_value(HashingStrategy strategy = HashingStrategy::kDefault) const;
  auto options() const { return std::tuple{cases, default_case, default_hint}; }
};

template <>
struct fast_hash<SwitchOp::Case> {
  size_t operator()(SwitchOp::Case v) {
    return fast_hash_combine(v.value, v.destination);
  }
};

inline base::SmallVector<Block*, 4> SuccessorBlocks(const Operation& op) {
  switch (op.opcode) {
    case Opcode::kCheckException: {
      auto& cast = op.Cast<CheckExceptionOp>();
      base::SmallVector<Block*, 4> res{cast.didnt_throw_block};
      if (cast.catch_block) {
        res.push_back(cast.catch_block);
      }
      for (auto& effect_handler : cast.effect_handlers) {
        if (!effect_handler.is_switch()) {
          res.push_back(effect_handler.block);
        }
      }
      return res;
    }
    case Opcode::kGoto: {
      auto& casted = op.Cast<GotoOp>();
      return {casted.destination};
    }
    case Opcode::kBranch: {
      auto& casted = op.Cast<BranchOp>();
      return {casted.if_true, casted.if_false};
    }
    case Opcode::kReturn:
    case Opcode::kTailCall:
    case Opcode::kDeoptimize:
    case Opcode::kUnreachable:
#if V8_ENABLE_WEBASSEMBLY
    case Opcode::kWasmTrap:
#endif
      return base::SmallVector<Block*, 4>{};
    case Opcode::kSwitch: {
      auto& casted = op.Cast<SwitchOp>();
      base::SmallVector<Block*, 4> result;
      for (const SwitchOp::Case& c : casted.cases) {
        result.push_back(c.destination);
      }
      result.push_back(casted.default_case);
      return result;
    }
#define NON_TERMINATOR_CASE(op) case Opcode::k##op:
      TURBOSHAFT_OPERATION_LIST_NOT_BLOCK_TERMINATOR(NON_TERMINATOR_CASE)
      UNREACHABLE();
#undef NON_TERMINATOR_CASE
  }
  UNREACHABLE();
}

V8_EXPORT_PRIVATE base::SmallVector<Block*, 4> SuccessorBlocks(
    const Block& block, const Graph& graph);

// Tuples are only used to lower operations with multiple outputs.
// `MakeTupleOp` should be folded away by subsequent `ProjectionOp`s.
struct MakeTupleOp : OperationT<MakeTupleOp> {
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit MakeTupleOp(base::Vector<const V<Any>> inputs) : Base(inputs) {}

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    auto mapped_inputs = mapper.template Map<4>(inputs());
    return fn(base::VectorOf(mapped_inputs));
  }

  auto options() const { return std::tuple{}; }
};

// For operations that produce multiple results, we use `ProjectionOp` to
// distinguish them.
struct ProjectionOp : FixedArityOperationT<1, ProjectionOp> {
  uint16_t index;
  RegisterRepresentation rep;

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&rep, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  V<Any> input() const { return Base::input<Any>(0); }

  ProjectionOp(V<Any> input, uint16_t index, RegisterRepresentation rep)
      : Base(input), index(index), rep(rep) {}

  void Validate(const Graph& graph) const {
#ifdef DEBUG
    ValidateOpInputRep(graph, input(), rep, this, index);
#endif  // DEBUG
  }
  auto options() const { return std::tuple{index, rep}; }
};

struct CheckTurboshaftTypeOfOp
    : FixedArityOperationT<1, CheckTurboshaftTypeOfOp> {
  RegisterRepresentation rep;
  Type type;
  bool successful;

  static constexpr OpEffects effects = OpEffects()
                                           .CanDependOnChecks()
                                           .CanReadImmutableMemory()
                                           .RequiredWhenUnused();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  OpIndex input() const { return Base::input(0); }

  CheckTurboshaftTypeOfOp(OpIndex input, RegisterRepresentation rep, Type type,
                          bool successful)
      : Base(input), rep(rep), type(std::move(type)), successful(successful) {}

  auto options() const { return std::tuple{rep, type, successful}; }
};

struct CheckMaglevTypeOp : FixedArityOperationT<1, CheckMaglevTypeOp> {
  maglev::NodeType type;

  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanReadMemory().RequiredWhenUnused();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }

  CheckMaglevTypeOp(V<Object> input, maglev::NodeType type)
      : Base(input), type(type) {}

  auto options() const { return std::tuple{type}; }
};

struct ObjectIsOp : FixedArityOperationT<1, ObjectIsOp> {
  enum class Kind : uint8_t {
    kArrayBufferView,
    kBigInt,
    kBigInt64,
    kCallable,
    kConstructor,
    kDetectableCallable,
    kInternalizedString,
    kNonCallable,
    kNumber,
    kNumberOrUndefined,
    kNumberFitsInt32,
    kNumberOrBigInt,
    kReceiver,
    kReceiverOrNullOrUndefined,
    kSmi,
    kString,
    kStringOrStringWrapper,
    kStringOrOddball,
    kSymbol,
    kUndetectable,
  };
  enum class InputAssumptions : uint8_t {
    kNone,
    kHeapObject,
    kBigInt,
  };
  Kind kind;
  InputAssumptions input_assumptions;

  // All type checks performed by this operator are regarding immutable
  // properties. Therefore, it can be considered pure. Input assumptions,
  // howerever, can rely on being scheduled after checks.
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanReadImmutableMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }

  ObjectIsOp(V<Object> input, Kind kind, InputAssumptions input_assumptions)
      : Base(input), kind(kind), input_assumptions(input_assumptions) {}

  auto options() const { return std::tuple{kind, input_assumptions}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ObjectIsOp::Kind kind);
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os, ObjectIsOp::InputAssumptions input_assumptions);

enum class NumericKind : uint8_t {
  kFloat64Hole,
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
  kFloat64Undefined,
  kFloat64UndefinedOrHole,
#endif  // V8_ENABLE_UNDEFINED_DOUBLE
  kFinite,
  kInteger,
  kSafeInteger,
  kInt32,
  kSmi,
  kMinusZero,
  kNaN,
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os, NumericKind kind);

struct FloatIsOp : FixedArityOperationT<1, FloatIsOp> {
  NumericKind kind;
  FloatRepresentation rep;

  FloatIsOp(V<Float> input, NumericKind kind, FloatRepresentation rep)
      : Base(input), kind(kind), rep(rep) {}

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  V<Float> input() const { return Base::input<Float>(0); }

  auto options() const { return std::tuple{kind, rep}; }
};

struct ObjectIsNumericValueOp
    : FixedArityOperationT<1, ObjectIsNumericValueOp> {
  NumericKind kind;
  FloatRepresentation input_rep;

  ObjectIsNumericValueOp(V<Object> input, NumericKind kind,
                         FloatRepresentation input_rep)
      : Base(input), kind(kind), input_rep(input_rep) {}

  // Heap numbers are immutable, so reading from them is pure. We might rely on
  // checks to assume that the input is a heap number.
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanReadImmutableMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }

  auto options() const { return std::tuple{kind, input_rep}; }
};

struct ConvertOp : FixedArityOperationT<1, ConvertOp> {
  enum class Kind : uint8_t {
    kObject,
    kBoolean,
    kNumber,
    kNumberOrOddball,
    kPlainPrimitive,
    kString,
    kSmi,
  };
  Kind from;
  Kind to;

  // All properties/values we read are immutable.
  static constexpr OpEffects effects =
      OpEffects()
          // We only allocate identityless primitives here.
          .CanAllocateWithoutIdentity()
          // We might use preceding checks to ensure the input has the right
          // type.
          .CanDependOnChecks()
          .CanReadImmutableMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }

  ConvertOp(V<Object> input, Kind from, Kind to)
      : Base(input), from(from), to(to) {}

  auto options() const { return std::tuple{from, to}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ConvertOp::Kind kind);

struct ConvertUntaggedToJSPrimitiveOp
    : FixedArityOperationT<1, ConvertUntaggedToJSPrimitiveOp> {
  enum class JSPrimitiveKind : uint8_t {
    kBigInt,
    kBoolean,
    kHeapNumber,
    kHeapNumberOrUndefined,
    kNumber,
    kSmi,
    kString,
  };
  enum class InputInterpretation : uint8_t {
    kSigned,
    kUnsigned,
    kDouble,
    kDoubleOrHole,
    kDoubleOrUndefined,
    kDoubleOrUndefinedOrHole,
    kCharCode,
    kCodePoint,
  };
  JSPrimitiveKind kind;
  RegisterRepresentation input_rep;
  InputInterpretation input_interpretation;
  CheckForMinusZeroMode minus_zero_mode;

  // The input is untagged and the results are identityless primitives.
  static constexpr OpEffects effects = OpEffects().CanAllocateWithoutIdentity();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(input_rep);
  }

  template <IsUntagged T>
  V<T> input() const {
    DCHECK_EQ(V<T>::rep, input_rep);
    return Base::input<T>(0);
  }

  ConvertUntaggedToJSPrimitiveOp(V<Untagged> input, JSPrimitiveKind kind,
                                 RegisterRepresentation input_rep,
                                 InputInterpretation input_interpretation,
                                 CheckForMinusZeroMode minus_zero_mode)
      : Base(input),
        kind(kind),
        input_rep(input_rep),
        input_interpretation(input_interpretation),
        minus_zero_mode(minus_zero_mode) {
    DCHECK_EQ(input_rep == RegisterRepresentation::Float64(),
              (input_interpretation ==
               any_of(InputInterpretation::kDouble,
                      InputInterpretation::kDoubleOrHole,
                      InputInterpretation::kDoubleOrUndefined,
                      InputInterpretation::kDoubleOrUndefinedOrHole)));
  }

  void Validate(const Graph& graph) const {
    switch (kind) {
      case JSPrimitiveKind::kBigInt:
        DCHECK_EQ(input_rep, RegisterRepresentation::Word64());
        DCHECK_EQ(minus_zero_mode,
                  CheckForMinusZeroMode::kDontCheckForMinusZero);
        break;
      case JSPrimitiveKind::kBoolean:
        DCHECK_EQ(input_rep, RegisterRepresentation::Word32());
        DCHECK_EQ(minus_zero_mode,
                  CheckForMinusZeroMode::kDontCheckForMinusZero);
        break;
      case JSPrimitiveKind::kNumber:
      case JSPrimitiveKind::kHeapNumber:
        DCHECK_IMPLIES(
            minus_zero_mode == CheckForMinusZeroMode::kCheckForMinusZero,
            input_rep == RegisterRepresentation::Float64());
        break;
      case JSPrimitiveKind::kHeapNumberOrUndefined:
        DCHECK_IMPLIES(
            minus_zero_mode == CheckForMinusZeroMode::kDontCheckForMinusZero,
            input_rep == RegisterRepresentation::Float64());
        break;
      case JSPrimitiveKind::kSmi:
        DCHECK_EQ(input_rep, WordRepresentation::Word32());
        DCHECK_EQ(minus_zero_mode,
                  CheckForMinusZeroMode::kDontCheckForMinusZero);
        break;
      case JSPrimitiveKind::kString:
        DCHECK_EQ(input_rep, WordRepresentation::Word32());
        DCHECK_EQ(input_interpretation,
                  any_of(InputInterpretation::kCharCode,
                         InputInterpretation::kCodePoint));
        break;
    }
  }

  auto options() const {
    return std::tuple{kind, input_rep, input_interpretation, minus_zero_mode};
  }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os, ConvertUntaggedToJSPrimitiveOp::JSPrimitiveKind kind);

struct ConvertWordToSmiOrDeoptOp
    : FixedArityOperationT<2, ConvertWordToSmiOrDeoptOp> {
  enum class InputInterpretation : uint8_t {
    kSigned,
    kUnsigned,
  };
  RegisterRepresentation input_rep;
  InputInterpretation input_interpretation;
  FeedbackSource feedback;

  // We currently only convert Word representations to Smi or deopt. We need to
  // change the effects if we add more kinds.
  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(input_rep);
  }

  V<Word> input() const { return Base::input<Word>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }

  ConvertWordToSmiOrDeoptOp(V<Word> input, V<EagerFrameState> frame_state,
                            RegisterRepresentation input_rep,
                            InputInterpretation input_interpretation,
                            const FeedbackSource& feedback)
      : Base(input, frame_state),
        input_rep(input_rep),
        input_interpretation(input_interpretation),
        feedback(feedback) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const {
    return std::tuple{input_rep, input_interpretation, feedback};
  }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os,
    ConvertWordToSmiOrDeoptOp::InputInterpretation input_interpretation);

struct ConvertJSPrimitiveToUntaggedOp
    : FixedArityOperationT<1, ConvertJSPrimitiveToUntaggedOp> {
  enum class UntaggedKind : uint8_t {
    kInt32,
    kInt64,
    kUint32,
    kBit,
    kFloat64,
    kHoleyFloat64,
  };
  enum class InputAssumptions : uint8_t {
    kBoolean,
    kSmi,
    kSmiOrHole,
    kNumberOrOddball,
    kNumberOrHole,
    kPlainPrimitive,
  };
  UntaggedKind kind;
  InputAssumptions input_assumptions;

  // This operation can read memory, but only immutable aspects, so it counts as
  // pure.
  static constexpr OpEffects effects =
      OpEffects()
          // We might rely on preceding checks to avoid deopts.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (kind) {
      case UntaggedKind::kInt32:
      case UntaggedKind::kUint32:
      case UntaggedKind::kBit:
        return RepVector<RegisterRepresentation::Word32()>();
      case UntaggedKind::kInt64:
        return RepVector<RegisterRepresentation::Word64()>();
      case UntaggedKind::kFloat64:
      case UntaggedKind::kHoleyFloat64:
        return RepVector<RegisterRepresentation::Float64()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSPrimitive> input() const { return Base::input<JSPrimitive>(0); }

  ConvertJSPrimitiveToUntaggedOp(V<JSPrimitive> input, UntaggedKind kind,
                                 InputAssumptions input_assumptions)
      : Base(input), kind(kind), input_assumptions(input_assumptions) {}

  auto options() const { return std::tuple{kind, input_assumptions}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os, ConvertJSPrimitiveToUntaggedOp::UntaggedKind kind);
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os,
    ConvertJSPrimitiveToUntaggedOp::InputAssumptions input_assumptions);

struct ConvertJSPrimitiveToUntaggedOrDeoptOp
    : FixedArityOperationT<2, ConvertJSPrimitiveToUntaggedOrDeoptOp> {
  enum class UntaggedKind : uint8_t {
    kInt32,
    kAdditiveSafeInteger,
    kInt64,
    kUint64,
    kFloat64,
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
    kHoleyFloat64,
#endif  // V8_ENABLE_UNDEFINED_DOUBLE
    kArrayIndex,
  };
  enum class JSPrimitiveKind : uint8_t {
    kAdditiveSafeInteger,
    kNumber,
    kNumberOrUndefined,
    kNumberOrBoolean,
    kNumberOrOddball,
    kNumberOrString,
    kSmi,
  };
  JSPrimitiveKind from_kind;
  UntaggedKind to_kind;
  CheckForMinusZeroMode minus_zero_mode;
  FeedbackSource feedback;

  // This operation can read memory, but only immutable aspects, so it counts as
  // pure.
  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (to_kind) {
      case UntaggedKind::kInt32:
        return RepVector<RegisterRepresentation::Word32()>();
      case UntaggedKind::kAdditiveSafeInteger:
      case UntaggedKind::kInt64:
      case UntaggedKind::kUint64:
        return RepVector<RegisterRepresentation::Word64()>();
      case UntaggedKind::kFloat64:
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
      case UntaggedKind::kHoleyFloat64:
#endif  // V8_ENABLE_UNDEFINED_DOUBLE
        return RepVector<RegisterRepresentation::Float64()>();
      case UntaggedKind::kArrayIndex:
        return Is64() ? RepVector<RegisterRepresentation::Word64()>()
                      : RepVector<RegisterRepresentation::Word32()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }

  ConvertJSPrimitiveToUntaggedOrDeoptOp(V<Object> input,
                                        V<EagerFrameState> frame_state,
                                        JSPrimitiveKind from_kind,
                                        UntaggedKind to_kind,
                                        CheckForMinusZeroMode minus_zero_mode,
                                        const FeedbackSource& feedback)
      : Base(input, frame_state),
        from_kind(from_kind),
        to_kind(to_kind),
        minus_zero_mode(minus_zero_mode),
        feedback(feedback) {}
  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const {
    return std::tuple{from_kind, to_kind, minus_zero_mode, feedback};
  }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os,
    ConvertJSPrimitiveToUntaggedOrDeoptOp::JSPrimitiveKind kind);
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os, ConvertJSPrimitiveToUntaggedOrDeoptOp::UntaggedKind kind);

struct TruncateJSPrimitiveToUntaggedOp
    : FixedArityOperationT<1, TruncateJSPrimitiveToUntaggedOp> {
  enum class UntaggedKind : uint8_t {
    kInt32,
    kInt64,
    kBit,
  };
  enum class InputAssumptions : uint8_t {
    kBigInt,
    kSmiOrHole,
    kNumberOrOddball,
    kNumberOrOddballOrHole,
    kHeapObject,
    kObject,
  };
  UntaggedKind kind;
  InputAssumptions input_assumptions;

  // This operation can read memory, but only immutable aspects, so it counts as
  // pure.
  static constexpr OpEffects effects =
      OpEffects()
          // We might rely on preceding checks to ensure the input type.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (kind) {
      case UntaggedKind::kInt32:
      case UntaggedKind::kBit:
        return RepVector<RegisterRepresentation::Word32()>();
      case UntaggedKind::kInt64:
        return RepVector<RegisterRepresentation::Word64()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSPrimitive> input() const { return Base::input<JSPrimitive>(0); }

  TruncateJSPrimitiveToUntaggedOp(V<JSPrimitive> input, UntaggedKind kind,
                                  InputAssumptions input_assumptions)
      : Base(input), kind(kind), input_assumptions(input_assumptions) {}

  auto options() const { return std::tuple{kind, input_assumptions}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os, TruncateJSPrimitiveToUntaggedOp::UntaggedKind kind);
V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os,
    TruncateJSPrimitiveToUntaggedOp::InputAssumptions input_assumptions);

DEFINE_MULTI_SWITCH_INTEGRAL(TruncateJSPrimitiveToUntaggedOp::UntaggedKind, 4)

struct TruncateJSPrimitiveToWord32OrDeoptOp
    : FixedArityOperationT<2, TruncateJSPrimitiveToWord32OrDeoptOp> {
  using InputRequirement =
      ConvertJSPrimitiveToUntaggedOrDeoptOp::JSPrimitiveKind;
  InputRequirement input_requirement;
  FeedbackSource feedback;

  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
        return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSPrimitive> input() const { return Base::input<JSPrimitive>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }

  TruncateJSPrimitiveToWord32OrDeoptOp(V<JSPrimitive> input,
                                       V<EagerFrameState> frame_state,
                                       InputRequirement input_requirement,
                                       const FeedbackSource& feedback)
      : Base(input, frame_state),
        input_requirement(input_requirement),
        feedback(feedback) {}
  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple{input_requirement, feedback}; }
};

struct ConvertJSPrimitiveToObjectOp
    : FixedArityOperationT<3, ConvertJSPrimitiveToObjectOp> {
  ConvertReceiverMode mode;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSPrimitive> value() const { return Base::input<JSPrimitive>(0); }
  V<Context> native_context() const { return Base::input<Context>(1); }
  V<JSGlobalProxy> global_proxy() const {
    return Base::input<JSGlobalProxy>(2);
  }

  ConvertJSPrimitiveToObjectOp(V<JSPrimitive> value, V<Context> native_context,
                               V<JSGlobalProxy> global_proxy,
                               ConvertReceiverMode mode)
      : Base(value, native_context, global_proxy), mode(mode) {}


  auto options() const { return std::tuple{mode}; }
};

struct NewConsStringOp : FixedArityOperationT<3, NewConsStringOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // Strings are conceptually immutable and don't have identity.
          .CanAllocateWithoutIdentity()
          // We might rely on preceding checks to ensure the input is a string
          // and on their combined length being between ConsString::kMinLength
          // and ConsString::kMaxLength.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<Word32> length() const { return Base::input<Word32>(0); }
  V<String> first() const { return Base::input<String>(1); }
  V<String> second() const { return Base::input<String>(2); }

  NewConsStringOp(V<Word32> length, V<String> first, V<String> second)
      : Base(length, first, second) {}

  auto options() const { return std::tuple{}; }
};

struct NewArrayOp : FixedArityOperationT<1, NewArrayOp> {
  enum class Kind : uint8_t {
    kDouble,
    kObject,
  };
  Kind kind;
  AllocationType allocation_type;

  static constexpr OpEffects effects =
      OpEffects()
          // Allocate the result, which has identity.
          .CanAllocate()
          // We might have checks to ensure the array length is valid and not
          // too big.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr()>();
  }

  OpIndex length() const { return Base::input(0); }

  NewArrayOp(OpIndex length, Kind kind, AllocationType allocation_type)
      : Base(length), kind(kind), allocation_type(allocation_type) {}

  auto options() const { return std::tuple{kind, allocation_type}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           NewArrayOp::Kind kind);

struct DoubleArrayMinMaxOp : FixedArityOperationT<1, DoubleArrayMinMaxOp> {
  enum class Kind : uint8_t {
    kMin,
    kMax,
  };
  Kind kind;

  static constexpr OpEffects effects =
      OpEffects()
          // Read the array contents.
          .CanReadHeapMemory()
          // Allocate the HeapNumber result.
          .CanAllocateWithoutIdentity()
          // We might depend on checks to ensure the input is an array.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSArray> array() const { return Base::input<JSArray>(0); }

  DoubleArrayMinMaxOp(V<JSArray> array, Kind kind) : Base(array), kind(kind) {}

  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           DoubleArrayMinMaxOp::Kind kind);

// TODO(nicohartmann@): We should consider getting rid of the LoadFieldByIndex
// operation.
struct LoadFieldByIndexOp : FixedArityOperationT<2, LoadFieldByIndexOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // Read the possibly mutable property.
          .CanReadHeapMemory()
          // We may allocate heap number for the result.
          .CanAllocateWithoutIdentity()
          // We assume the input is an object.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  OpIndex object() const { return Base::input(0); }
  // Index encoding (see `src/objects/field-index-inl.h`):
  // For efficiency, the LoadByFieldIndex instruction takes an index that is
  // optimized for quick access. If the property is inline, the index is
  // positive. If it's out-of-line, the encoded index is -raw_index - 1 to
  // disambiguate the zero out-of-line index from the zero inobject case.
  // The index itself is shifted up by one bit, the lower-most bit
  // signifying if the field is a mutable double box (1) or not (0).
  OpIndex index() const { return Base::input(1); }

  LoadFieldByIndexOp(OpIndex object, OpIndex index) : Base(object, index) {}

  auto options() const { return std::tuple{}; }
};

struct LoadDictionaryFieldOp : FixedArityOperationT<4, LoadDictionaryFieldOp> {
  InternalIndex index;
  compiler::NameRef name;
  FeedbackSource feedback;
  bool is_super;

  THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged())
  static constexpr OpEffects effects =
      OpEffects().CanCallAnything().CanThrowOrTrap();

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSReceiver> object() const { return Base::input<JSReceiver>(0); }
  V<Object> receiver() const { return Base::input<Object>(1); }
  V<Context> context() const { return Base::input<Context>(2); }
  V<LazyFrameState> frame_state() const {
    return Base::input<LazyFrameState>(3);
  }

  LoadDictionaryFieldOp(V<JSReceiver> object, V<Object> receiver,
                        V<Context> context, V<LazyFrameState> frame_state,
                        size_t index, compiler::NameRef name,
                        const FeedbackSource& feedback, bool is_super,
                        LazyDeoptOnThrow lazy_deopt_on_throw)
      : Base(object, receiver, context, frame_state),
        index(InternalIndex(index)),
        name(name),
        feedback(feedback),
        is_super(is_super),
        lazy_deopt_on_throw(lazy_deopt_on_throw) {}

  auto options() const {
    return std::tuple{index.raw_value(), name, feedback, is_super,
                      lazy_deopt_on_throw};
  }
};

struct DebugBreakOp : FixedArityOperationT<0, DebugBreakOp> {
  // Prevent any reordering.
  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  DebugBreakOp() : Base() {}

  auto options() const { return std::tuple{}; }
};

struct DebugPrintOp : OperationT<DebugPrintOp> {
  RegisterRepresentation rep;

  // We just need to ensure that the debug print stays in the same block and
  // observes the right memory state. It doesn't actually change control flow,
  // but pretending so ensures the we do not remove the debug print even though
  // it is unused. We assume that the debug print doesn't affect memory so that
  // the scheduling of loads is not affected.
  static constexpr OpEffects effects = OpEffects()
                                           .CanChangeControlFlow()
                                           .CanDependOnChecks()
                                           .CanReadMemory()
                                           .RequiredWhenUnused();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InputsRepFactory::SingleRep(rep);
  }

  OpIndex value() const { return Base::input(0); }
  OptionalV<String> label() const {
    return input_count >= 2 ? Base::input<String>(1)
                            : OptionalV<String>::Nullopt();
  }

  DebugPrintOp(OpIndex value, OptionalV<String> label,
               RegisterRepresentation rep)
      : Base(1 + label.has_value()), rep(rep) {
    input(0) = value;
    if (label.has_value()) {
      input(1) = label.value();
    }
  }

  auto options() const { return std::tuple{rep}; }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(value()), mapper.Map(label()), rep);
  }

  static DebugPrintOp& New(Graph* graph, OpIndex value, OptionalV<String> label,
                           RegisterRepresentation rep) {
    return Base::New(graph, 1 + label.has_value(), value, label, rep);
  }
};

struct BigIntBinopOp : FixedArityOperationT<3, BigIntBinopOp> {
  enum class Kind : uint8_t {
    kAdd,
    kSub,
    kMul,
    kDiv,
    kMod,
    kBitwiseAnd,
    kBitwiseOr,
    kBitwiseXor,
    kShiftLeft,
    kShiftRightArithmetic,
  };
  Kind kind;

  // These operations can deopt (abort), allocate and read immutable data.
  static constexpr OpEffects effects =
      OpEffects()
          // Allocate the resulting BigInt, which does not have identity.
          .CanAllocateWithoutIdentity()
          .CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<BigInt> left() const { return Base::input<BigInt>(0); }
  V<BigInt> right() const { return Base::input<BigInt>(1); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(2);
  }

  BigIntBinopOp(V<BigInt> left, V<BigInt> right, V<EagerFrameState> frame_state,
                Kind kind)
      : Base(left, right, frame_state), kind(kind) {}
  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           BigIntBinopOp::Kind kind);

struct BigIntComparisonOp : FixedArityOperationT<2, BigIntComparisonOp> {
  enum class Kind : uint8_t {
    kEqual,
    kLessThan,
    kLessThanOrEqual,
  };
  Kind kind;

  static constexpr OpEffects effects =
      OpEffects()
          // We rely on the inputs having BigInt type.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  static bool IsCommutative(Kind kind) { return kind == Kind::kEqual; }

  V<BigInt> left() const { return Base::input<BigInt>(0); }
  V<BigInt> right() const { return Base::input<BigInt>(1); }

  BigIntComparisonOp(V<BigInt> left, V<BigInt> right, Kind kind)
      : Base(left, right), kind(kind) {}


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           BigIntComparisonOp::Kind kind);

struct BigIntUnaryOp : FixedArityOperationT<1, BigIntUnaryOp> {
  enum class Kind : uint8_t {
    kNegate,
  };
  Kind kind;

  static constexpr OpEffects effects =
      OpEffects()
          // BigInt content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the input being a BigInt.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<BigInt> input() const { return Base::input<BigInt>(0); }

  BigIntUnaryOp(V<BigInt> input, Kind kind) : Base(input), kind(kind) {}


  auto options() const { return std::tuple{kind}; }
};

V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           BigIntUnaryOp::Kind kind);

struct LoadRootRegisterOp : FixedArityOperationT<0, LoadRootRegisterOp> {
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::WordPtr()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  LoadRootRegisterOp() : Base() {}
  std::tuple<> options() const { return {}; }
};

struct StringAtOp : FixedArityOperationT<2, StringAtOp> {
  enum class Kind : uint8_t {
    kCharCode,
    kCodePoint,
  };
  Kind kind;

  static constexpr OpEffects effects =
      // String content is immutable, so this operation is pure.
      OpEffects()
          // We rely on the input being a string.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  V<String> string() const { return Base::input<String>(0); }
  V<WordPtr> position() const { return Base::input<WordPtr>(1); }

  StringAtOp(V<String> string, V<WordPtr> position, Kind kind)
      : Base(string, position), kind(kind) {}


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           StringAtOp::Kind kind);

#ifdef V8_INTL_SUPPORT
struct StringToCaseIntlOp : FixedArityOperationT<3, StringToCaseIntlOp> {
  enum class Kind : uint8_t {
    kLower,
    kUpper,
  };
  Kind kind;

  static constexpr OpEffects effects =
      OpEffects()
          // Can thrown an exception.
          .CanThrowOrTrap()
          // String content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the input being a string.
          .CanDependOnChecks();

  THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged())

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<String> string() const { return Base::input<String>(0); }
  V<LazyFrameState> frame_state() const {
    return Base::input<LazyFrameState>(1);
  }
  V<Context> context() const { return Base::input<Context>(2); }

  StringToCaseIntlOp(V<String> string, V<LazyFrameState> frame_state,
                     V<Context> context, Kind kind,
                     LazyDeoptOnThrow lazy_deopt_on_throw)
      : Base(string, frame_state, context),
        kind(kind),
        lazy_deopt_on_throw(lazy_deopt_on_throw) {}

  auto options() const { return std::tuple{kind, lazy_deopt_on_throw}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           StringToCaseIntlOp::Kind kind);

// Inlines the ASCII fast path of String.prototype.localeCompare. Lowering
// emits a runtime check + byte-equality prefix loop, falling back to the
// kStringFastLocaleCompare builtin on bailout. `left` and `right` are
// V<Object>: the spec performs ToString on both, so non-strings flow through
// the bailout.
struct StringLocaleCompareIntlOp
    : FixedArityOperationT<6, StringLocaleCompareIntlOp> {
  // TODO(jgruber): Narrow effects when both inputs are known strings.
  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  THROWING_OP_BOILERPLATE(RegisterRepresentation::Tagged())

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSFunction> locale_compare_fn() const { return Base::input<JSFunction>(0); }
  V<Object> left() const { return Base::input<Object>(1); }
  V<Object> right() const { return Base::input<Object>(2); }
  V<StringOrUndefined> locales() const {
    return Base::input<StringOrUndefined>(3);
  }
  V<LazyFrameState> frame_state() const {
    return Base::input<LazyFrameState>(4);
  }
  V<Context> context() const { return Base::input<Context>(5); }

  StringLocaleCompareIntlOp(V<JSFunction> locale_compare_fn, V<Object> left,
                            V<Object> right, V<StringOrUndefined> locales,
                            V<LazyFrameState> frame_state, V<Context> context,
                            LazyDeoptOnThrow lazy_deopt_on_throw)
      : Base(locale_compare_fn, left, right, locales, frame_state, context),
        lazy_deopt_on_throw(lazy_deopt_on_throw) {}

  auto options() const { return std::tuple{lazy_deopt_on_throw}; }
};
#endif  // V8_INTL_SUPPORT

struct StringLengthOp : FixedArityOperationT<1, StringLengthOp> {
  static constexpr OpEffects effects =
      // String content is immutable, so this operation is pure.
      OpEffects()
          // We rely on the input being a string.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<String> string() const { return Base::input<String>(0); }

  explicit StringLengthOp(V<String> string) : Base(string) {}


  auto options() const { return std::tuple{}; }
};

struct TypedArrayLengthOp : FixedArityOperationT<1, TypedArrayLengthOp> {
  ElementsKind elements_kind;

  static constexpr OpEffects effects =
      // This operation is only triggered for constant-length JSTypedArrays, so
      // it's pure.
      OpEffects()
          // We rely on the input being a JSTypedArray.
          .CanDependOnChecks()
          .CanReadMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::WordPtr()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSTypedArray> typed_array() const { return Base::input<JSTypedArray>(0); }

  explicit TypedArrayLengthOp(V<JSTypedArray> typed_array,
                              ElementsKind elements_kind)
      : Base(typed_array), elements_kind(elements_kind) {}

  auto options() const { return std::tuple{elements_kind}; }
};

struct StringIndexOfOp : FixedArityOperationT<3, StringIndexOfOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // String content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the inputs being strings.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  // Search the string `search` within the string `string` starting at
  // `position`.
  V<String> string() const { return Base::input<String>(0); }
  V<String> search() const { return Base::input<String>(1); }
  V<Smi> position() const { return Base::input<Smi>(2); }

  StringIndexOfOp(V<String> string, V<String> search, V<Smi> position)
      : Base(string, search, position) {}


  auto options() const { return std::tuple{}; }
};

struct StringFromCodePointAtOp
    : FixedArityOperationT<2, StringFromCodePointAtOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // String content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the input being in a certain char range.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  V<String> string() const { return Base::input<String>(0); }
  V<WordPtr> index() const { return Base::input<WordPtr>(1); }

  StringFromCodePointAtOp(V<String> string, V<WordPtr> index)
      : Base(string, index) {}


  auto options() const { return std::tuple{}; }
};

struct StringSubstringOp : FixedArityOperationT<3, StringSubstringOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // String content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the input being a string.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  V<String> string() const { return Base::input<String>(0); }
  V<Word32> start() const { return Base::input<Word32>(1); }
  V<Word32> end() const { return Base::input<Word32>(2); }

  StringSubstringOp(V<String> string, V<Word32> start, V<Word32> end)
      : Base(string, start, end) {}


  auto options() const { return std::tuple{}; }
};

struct StringSliceOp : FixedArityOperationT<3, StringSliceOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // String content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the input being a string.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  V<String> string() const { return Base::input<String>(0); }

  StringSliceOp(V<String> string, V<Word32> start, V<Word32> end)
      : Base(string, start, end) {}

  auto options() const { return std::tuple{}; }
};

struct StringConcatOp : FixedArityOperationT<3, StringConcatOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // String content is immutable, the allocated result does not have
          // identity.
          .CanAllocateWithoutIdentity()
          // We rely on the inputs being strings.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<Smi> length() const { return Base::input<Smi>(0); }
  V<String> left() const { return Base::input<String>(1); }
  V<String> right() const { return Base::input<String>(2); }

  StringConcatOp(V<Smi> length, V<String> left, V<String> right)
      : Base(length, left, right) {}


  auto options() const { return std::tuple{}; }
};

struct StringComparisonOp : FixedArityOperationT<2, StringComparisonOp> {
  enum class Kind : uint8_t {
    kEqual,
    kLessThan,
    kLessThanOrEqual,
  };
  Kind kind;

  static constexpr OpEffects effects =
      // String content is immutable, so the operation is pure.
      OpEffects()
          // We rely on the input being strings.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  static bool IsCommutative(Kind kind) { return kind == Kind::kEqual; }

  V<String> left() const { return Base::input<String>(0); }
  V<String> right() const { return Base::input<String>(1); }

  StringComparisonOp(V<String> left, V<String> right, Kind kind)
      : Base(left, right), kind(kind) {}


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           StringComparisonOp::Kind kind);

struct StringOrOddballStrictEqualOp
    : FixedArityOperationT<2, StringOrOddballStrictEqualOp> {
  static constexpr OpEffects effects =
      // String content is immutable, so the operation is pure.
      OpEffects()
          // We rely on the input being strings or oddballs.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<HeapObject> left() const { return Base::input<HeapObject>(0); }
  V<HeapObject> right() const { return Base::input<HeapObject>(1); }

  StringOrOddballStrictEqualOp(V<HeapObject> left, V<HeapObject> right)
      : Base(left, right) {}

  auto options() const { return std::tuple{}; }
};

struct ArgumentsLengthOp : FixedArityOperationT<0, ArgumentsLengthOp> {
  enum class Kind : uint8_t {
    kArguments,
    kRest,
  };
  Kind kind;
  int formal_parameter_count =
      0;  // This field is unused for kind == kArguments.

  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit ArgumentsLengthOp(Kind kind, int formal_parameter_count)
      : Base(), kind(kind), formal_parameter_count(formal_parameter_count) {
    DCHECK_IMPLIES(kind == Kind::kArguments, formal_parameter_count == 0);
  }


  auto options() const { return std::tuple{kind, formal_parameter_count}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           ArgumentsLengthOp::Kind kind);

struct NewArgumentsElementsOp
    : FixedArityOperationT<1, NewArgumentsElementsOp> {
  CreateArgumentsType type;
  int formal_parameter_count;

  static constexpr OpEffects effects =
      OpEffects()
          // Allocate the fixed array, which has identity.
          .CanAllocate()
          // Do not move the allocation before checks/branches.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  OpIndex arguments_count() const { return Base::input(0); }

  NewArgumentsElementsOp(OpIndex arguments_count, CreateArgumentsType type,
                         int formal_parameter_count)
      : Base(arguments_count),
        type(type),
        formal_parameter_count(formal_parameter_count) {}


  auto options() const { return std::tuple{type, formal_parameter_count}; }
};

inline constexpr RegisterRepresentation RegisterRepresentationForArrayType(
    ExternalArrayType array_type) {
  switch (array_type) {
    case kExternalInt8Array:
    case kExternalUint8Array:
    case kExternalUint8ClampedArray:
    case kExternalInt16Array:
    case kExternalUint16Array:
    case kExternalInt32Array:
    case kExternalUint32Array:
    case kExternalFloat16Array:
      return RegisterRepresentation::Word32();
    case kExternalFloat32Array:
      return RegisterRepresentation::Float32();
    case kExternalFloat64Array:
      return RegisterRepresentation::Float64();
    case kExternalBigInt64Array:
    case kExternalBigUint64Array:
      return RegisterRepresentation::Word64();
  }
  UNREACHABLE();
}

inline base::Vector<const RegisterRepresentation> VectorForRep(
    RegisterRepresentation rep) {
  static constexpr std::array<RegisterRepresentation, 6> table{
      RegisterRepresentation::Word32(),  RegisterRepresentation::Word64(),
      RegisterRepresentation::Float32(), RegisterRepresentation::Float64(),
      RegisterRepresentation::Tagged(),  RegisterRepresentation::Compressed()};
  return base::VectorOf(&table[static_cast<size_t>(rep.value())], 1);
}

struct LoadTypedElementOp : FixedArityOperationT<4, LoadTypedElementOp> {
  ExternalArrayType array_type;

  static constexpr OpEffects effects =
      OpEffects()
          // We read mutable memory.
          .CanReadMemory()
          // We rely on the input type and a valid index.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return VectorForRep(RegisterRepresentationForArrayType(array_type));
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  OpIndex buffer() const { return Base::input(0); }
  OpIndex base() const { return Base::input(1); }
  OpIndex external() const { return Base::input(2); }
  OpIndex index() const { return Base::input(3); }

  LoadTypedElementOp(OpIndex buffer, OpIndex base, OpIndex external,
                     OpIndex index, ExternalArrayType array_type)
      : Base(buffer, base, external, index), array_type(array_type) {}


  auto options() const { return std::tuple{array_type}; }
};

struct LoadDataViewElementOp : FixedArityOperationT<4, LoadDataViewElementOp> {
  ExternalArrayType element_type;

  static constexpr OpEffects effects = OpEffects()
                                           // We read mutable memory.
                                           .CanReadMemory()
                                           // We rely on the input type.
                                           .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return VectorForRep(RegisterRepresentationForArrayType(element_type));
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  OpIndex object() const { return Base::input(0); }
  OpIndex storage() const { return Base::input(1); }
  OpIndex index() const { return Base::input(2); }
  OpIndex is_little_endian() const { return Base::input(3); }

  LoadDataViewElementOp(OpIndex object, OpIndex storage, OpIndex index,
                        OpIndex is_little_endian,
                        ExternalArrayType element_type)
      : Base(object, storage, index, is_little_endian),
        element_type(element_type) {}


  auto options() const { return std::tuple{element_type}; }
};

struct LoadStackArgumentOp : FixedArityOperationT<2, LoadStackArgumentOp> {
  // Stack arguments are immutable, so reading them is pure.
  static constexpr OpEffects effects =
      OpEffects()
          // We rely on the input being in bounds.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::WordPtr()>();
  }

  OpIndex base() const { return Base::input(0); }
  OpIndex index() const { return Base::input(1); }

  LoadStackArgumentOp(OpIndex base, OpIndex index) : Base(base, index) {}


  auto options() const { return std::tuple{}; }
};

struct StoreTypedElementOp : FixedArityOperationT<5, StoreTypedElementOp> {
  ExternalArrayType array_type;

  static constexpr OpEffects effects =
      OpEffects()
          // We are reading the backing store pointer and writing into it.
          .CanReadMemory()
          .CanWriteMemory()
          // We rely on the input type and a valid index.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(
        storage,
        {RegisterRepresentation::Tagged(), RegisterRepresentation::Tagged(),
         RegisterRepresentation::WordPtr(), RegisterRepresentation::WordPtr(),
         RegisterRepresentationForArrayType(array_type)});
  }

  OpIndex buffer() const { return Base::input(0); }
  OpIndex base() const { return Base::input(1); }
  OpIndex external() const { return Base::input(2); }
  OpIndex index() const { return Base::input(3); }
  OpIndex value() const { return Base::input(4); }

  StoreTypedElementOp(OpIndex buffer, OpIndex base, OpIndex external,
                      OpIndex index, OpIndex value,
                      ExternalArrayType array_type)
      : Base(buffer, base, external, index, value), array_type(array_type) {}


  auto options() const { return std::tuple{array_type}; }
};

struct StoreDataViewElementOp
    : FixedArityOperationT<5, StoreDataViewElementOp> {
  ExternalArrayType element_type;

  static constexpr OpEffects effects =
      OpEffects()
          // We are reading the backing store pointer and writing into it.
          .CanReadMemory()
          .CanWriteMemory()
          // We rely on the input type and a valid index.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(
        storage,
        {RegisterRepresentation::Tagged(), RegisterRepresentation::WordPtr(),
         RegisterRepresentation::WordPtr(),
         RegisterRepresentationForArrayType(element_type),
         RegisterRepresentation::Word32()});
  }

  OpIndex object() const { return Base::input(0); }
  OpIndex storage() const { return Base::input(1); }
  OpIndex index() const { return Base::input(2); }
  OpIndex value() const { return Base::input(3); }
  OpIndex is_little_endian() const { return Base::input(4); }

  StoreDataViewElementOp(OpIndex object, OpIndex storage, OpIndex index,
                         OpIndex value, OpIndex is_little_endian,
                         ExternalArrayType element_type)
      : Base(object, storage, index, value, is_little_endian),
        element_type(element_type) {}


  auto options() const { return std::tuple{element_type}; }
};

struct TransitionAndStoreArrayElementOp
    : FixedArityOperationT<3, TransitionAndStoreArrayElementOp> {
  enum class Kind : uint8_t {
    kElement,
    kNumberElement,
    kOddballElement,
    kNonNumberElement,
    kSignedSmallElement,
  };
  Kind kind;
  MaybeIndirectHandle<Map> fast_map;
  MaybeIndirectHandle<Map> double_map;

  static constexpr OpEffects effects =
      OpEffects()
          // We are reading and writing mutable memory.
          .CanReadHeapMemory()
          .CanWriteHeapMemory()
          // We rely on the input type and a valid index.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(
        storage, {RegisterRepresentation::Tagged(),
                  RegisterRepresentation::WordPtr(), value_representation()});
  }

  V<JSArray> array() const { return Base::input<JSArray>(0); }
  V<WordPtr> index() const { return Base::input<WordPtr>(1); }
  V<Any> value() const { return Base::input<Any>(2); }

  TransitionAndStoreArrayElementOp(V<JSArray> array, V<WordPtr> index,
                                   V<Any> value, Kind kind,
                                   MaybeIndirectHandle<Map> fast_map,
                                   MaybeIndirectHandle<Map> double_map)
      : Base(array, index, value),
        kind(kind),
        fast_map(fast_map),
        double_map(double_map) {}


  RegisterRepresentation value_representation() const {
    switch (kind) {
      case Kind::kElement:
      case Kind::kNonNumberElement:
      case Kind::kOddballElement:
        return RegisterRepresentation::Tagged();
      case Kind::kNumberElement:
        return RegisterRepresentation::Float64();
      case Kind::kSignedSmallElement:
        return RegisterRepresentation::Word32();
    }
    UNREACHABLE();
  }

  size_t hash_value(
      HashingStrategy strategy = HashingStrategy::kDefault) const {
    DCHECK_EQ(strategy, HashingStrategy::kDefault);
    return HashWithOptions(fast_map.address(), double_map.address());
  }

  bool operator==(const TransitionAndStoreArrayElementOp& other) const {
    return kind == other.kind && fast_map.equals(other.fast_map) &&
           double_map.equals(other.double_map);
  }

  auto options() const { return std::tuple{kind, fast_map, double_map}; }
};

V8_EXPORT_PRIVATE std::ostream& operator<<(
    std::ostream& os, TransitionAndStoreArrayElementOp::Kind kind);

struct CompareMapsOp : OperationT<CompareMapsOp> {
  ZoneRefSet<Map> maps;

  static constexpr OpEffects effects = OpEffects().CanReadHeapMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<HeapObject> heap_object() const { return Base::input<HeapObject>(0); }

  OptionalV<Map> map() const {
    return input_count > 1 ? input<Map>(1) : OptionalV<Map>::Nullopt();
  }

  CompareMapsOp(V<HeapObject> heap_object, OptionalV<Map> map,
                ZoneRefSet<Map> maps)
      : Base(1 + map.valid()), maps(std::move(maps)) {
    input(0) = heap_object;
    if (map.valid()) {
      input(1) = map.value();
    }
  }


  auto options() const { return std::tuple{maps}; }
  void PrintOptions(std::ostream& os) const;

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(heap_object()), mapper.Map(map()), maps);
  }

  static CompareMapsOp& New(Graph* graph, V<HeapObject> heap_object,
                            OptionalV<Map> map, ZoneRefSet<Map> maps) {
    return Base::New(graph, 1 + map.valid(), heap_object, map, maps);
  }
};

struct CheckMapsOp : OperationT<CheckMapsOp> {
  CheckMapsFlags flags;
  ZoneRefSet<Map> maps;
  FeedbackSource feedback;

  // TODO(tebbi): Map checks without map transitions have less effects.
  static constexpr OpEffects effects = OpEffects()
                                           .CanDependOnChecks()
                                           .CanDeopt()
                                           .CanReadHeapMemory()
                                           .CanWriteHeapMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<HeapObject> heap_object() const { return Base::input<HeapObject>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }
  OptionalV<Map> map() const {
    return input_count > 2 ? input<Map>(2) : OptionalV<Map>::Nullopt();
  }

  CheckMapsOp(V<HeapObject> heap_object, V<EagerFrameState> frame_state,
              OptionalV<Map> map, ZoneRefSet<Map> maps, CheckMapsFlags flags,
              const FeedbackSource& feedback)
      : Base(2 + map.valid()),
        flags(flags),
        maps(std::move(maps)),
        feedback(feedback) {
    input(0) = heap_object;
    input(1) = frame_state;
    if (map.valid()) {
      input(2) = map.value();
    }
  }

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple{maps, flags, feedback}; }
  void PrintOptions(std::ostream& os) const;

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(heap_object()), mapper.Map(frame_state()),
              mapper.Map(map()), maps, flags, feedback);
  }

  static CheckMapsOp& New(Graph* graph, V<HeapObject> heap_object,
                          V<EagerFrameState> frame_state, OptionalV<Map> map,
                          ZoneRefSet<Map> maps, CheckMapsFlags flags,
                          const FeedbackSource& feedback) {
    return Base::New(graph, 2 + map.valid(), heap_object, frame_state, map,
                     maps, flags, feedback);
  }
};

struct CheckHomomorphicOp : FixedArityOperationT<2, CheckHomomorphicOp> {
  NameRef name;
  WeakHomomorphicFixedArrayRef homomorphic_array;
  int handler_value;
  bool check_heap_object;
  FeedbackSource feedback;

  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanDeopt().CanReadHeapMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> heap_object() const { return Base::input<Object>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }

  CheckHomomorphicOp(V<Object> heap_object, V<EagerFrameState> frame_state,
                     NameRef name,
                     WeakHomomorphicFixedArrayRef homomorphic_array,
                     int handler_value, bool check_heap_object,
                     const FeedbackSource& feedback)
      : Base(heap_object, frame_state),
        name(name),
        homomorphic_array(homomorphic_array),
        handler_value(handler_value),
        check_heap_object(check_heap_object),
        feedback(feedback) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const {
    return std::tuple{name, homomorphic_array, handler_value, check_heap_object,
                      feedback};
  }
};

// AssumeMaps are inserted after CheckMaps have been lowered, in order to keep
// map information around and easily accessible for subsequent optimization
// passes (Load Elimination for instance can then use those AssumeMap to
// determine that some objects don't alias because they have different maps).
struct AssumeMapOp : FixedArityOperationT<1, AssumeMapOp> {
  ZoneRefSet<Map> maps;
  // AssumeMap should not be scheduled before the preceding CheckMaps
  static constexpr OpEffects effects = OpEffects()
                                           .CanDependOnChecks()
                                           .CanReadHeapMemory()
                                           .CanChangeControlFlow();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<HeapObject> heap_object() const { return Base::input<HeapObject>(0); }

  AssumeMapOp(V<HeapObject> heap_object, ZoneRefSet<Map> maps)
      : Base(heap_object), maps(std::move(maps)) {}


  auto options() const { return std::tuple{maps}; }
  void PrintOptions(std::ostream& os) const;
};

struct CheckedClosureOp : FixedArityOperationT<2, CheckedClosureOp> {
  Handle<FeedbackCell> feedback_cell;

  // We only check immutable aspects of the incoming value.
  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> input() const { return Base::input<Object>(0); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(1);
  }

  CheckedClosureOp(V<Object> input, V<EagerFrameState> frame_state,
                   Handle<FeedbackCell> feedback_cell)
      : Base(input, frame_state), feedback_cell(feedback_cell) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  bool operator==(const CheckedClosureOp& other) const {
    return feedback_cell.address() == other.feedback_cell.address();
  }
  size_t hash_value(
      HashingStrategy strategy = HashingStrategy::kDefault) const {
    DCHECK_EQ(strategy, HashingStrategy::kDefault);
    return HashWithOptions(feedback_cell.address());
  }

  auto options() const { return std::tuple{feedback_cell}; }
};

struct CheckEqualsInternalizedStringOp
    : FixedArityOperationT<3, CheckEqualsInternalizedStringOp> {
  static constexpr OpEffects effects = OpEffects().CanDeopt();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> expected() const { return Base::input<Object>(0); }
  V<Object> value() const { return Base::input<Object>(1); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(2);
  }

  CheckEqualsInternalizedStringOp(V<Object> expected, V<Object> value,
                                  V<EagerFrameState> frame_state)
      : Base(expected, value, frame_state) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple{}; }
};

struct LoadMessageOp : FixedArityOperationT<1, LoadMessageOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // We are reading the message from the isolate.
          .CanReadOffHeapMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr()>();
  }

  V<WordPtr> offset() const { return Base::input<WordPtr>(0); }

  explicit LoadMessageOp(V<WordPtr> offset) : Base(offset) {}


  auto options() const { return std::tuple{}; }
};

struct StoreMessageOp : FixedArityOperationT<2, StoreMessageOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // We are writing the message in the isolate.
          .CanWriteOffHeapMemory();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<WordPtr> offset() const { return Base::input<WordPtr>(0); }
  V<Object> object() const { return Base::input<Object>(1); }

  explicit StoreMessageOp(V<WordPtr> offset, V<Object> object)
      : Base(offset, object) {}


  auto options() const { return std::tuple{}; }
};

struct SameValueOp : FixedArityOperationT<2, SameValueOp> {
  enum class Mode : uint8_t {
    kSameValue,
    kSameValueNumbersOnly,
  };
  Mode mode;

  static constexpr OpEffects effects =
      OpEffects()
          // We might depend on the inputs being numbers.
          .CanDependOnChecks();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<Object> left() const { return Base::input<Object>(0); }
  V<Object> right() const { return Base::input<Object>(1); }

  SameValueOp(V<Object> left, V<Object> right, Mode mode)
      : Base(left, right), mode(mode) {}


  auto options() const { return std::tuple{mode}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           SameValueOp::Mode mode);

struct Float64SameValueOp : FixedArityOperationT<2, Float64SameValueOp> {
  static constexpr OpEffects effects = OpEffects();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Float64(),
                          MaybeRegisterRepresentation::Float64()>();
  }

  V<Float64> left() const { return Base::input<Float64>(0); }
  V<Float64> right() const { return Base::input<Float64>(1); }

  Float64SameValueOp(V<Float64> left, V<Float64> right) : Base(left, right) {}


  auto options() const { return std::tuple{}; }
};

struct FastApiCallParameters : public NON_EXPORTED_BASE(ZoneObject) {
  FastApiCallFunction c_function;

  const CFunctionInfo* c_signature() const { return c_function.signature; }

  explicit FastApiCallParameters(FastApiCallFunction c_function)
      : c_function(c_function) {}

  static const FastApiCallParameters* Create(FastApiCallFunction c_function,
                                             Zone* graph_zone) {
    return graph_zone->New<FastApiCallParameters>(c_function);
  }
};

struct FastApiCallOp : OperationT<FastApiCallOp> {
  static constexpr uint32_t kSuccessValue = 1;
  static constexpr uint32_t kFailureValue = 0;

  const FastApiCallParameters* parameters;
  base::Vector<const RegisterRepresentation> out_reps;
  LazyDeoptOnThrow lazy_deopt_on_throw;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  // There are three inputs that are not parameters, the frame state, the data
  // argument, and the context.
  static constexpr int kNumNonParamInputs = 3;

  // The outputs are produced by the `DidntThrow` operation.
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    DCHECK_EQ(inputs().size(),
              kNumNonParamInputs + parameters->c_signature()->ArgumentCount());
    storage.resize(inputs().size());
    storage[0] = MaybeRegisterRepresentation::None();
    storage[1] = MaybeRegisterRepresentation::Tagged();
    storage[2] = MaybeRegisterRepresentation::Tagged();
    for (unsigned i = 0; i < parameters->c_signature()->ArgumentCount(); ++i) {
      storage[i + kNumNonParamInputs] = argument_representation(i);
    }
    return base::VectorOf(storage);
  }

  MaybeRegisterRepresentation argument_representation(
      unsigned argument_index) const {
    const CTypeInfo& arg_type =
        parameters->c_signature()->ArgumentInfo(argument_index);
    uint8_t flags = static_cast<uint8_t>(arg_type.GetFlags());
    if (flags & (static_cast<uint8_t>(CTypeInfo::Flags::kEnforceRangeBit) |
                 static_cast<uint8_t>(CTypeInfo::Flags::kClampBit))) {
      return MaybeRegisterRepresentation::Float64();
    }
    switch (arg_type.GetType()) {
      case CTypeInfo::Type::kVoid:
        UNREACHABLE();
      case CTypeInfo::Type::kBool:
      case CTypeInfo::Type::kUint8:
      case CTypeInfo::Type::kInt32:
      case CTypeInfo::Type::kUint32:
        return MaybeRegisterRepresentation::Word32();
      case CTypeInfo::Type::kInt64:
      case CTypeInfo::Type::kUint64:
        return MaybeRegisterRepresentation::Word64();
      case CTypeInfo::Type::kV8Value:
      case CTypeInfo::Type::kApiObject:
      case CTypeInfo::Type::kPointer:
      case CTypeInfo::Type::kSeqOneByteString:
        return MaybeRegisterRepresentation::Tagged();
      case CTypeInfo::Type::kFloat32:
      case CTypeInfo::Type::kFloat64:
        return MaybeRegisterRepresentation::Float64();
      case CTypeInfo::Type::kAny:
        // As the register representation is unknown, just treat it as None
        // to prevent any validation.
        return MaybeRegisterRepresentation::None();
    }
    UNREACHABLE();
  }

  V<LazyFrameState> frame_state() const { return input<LazyFrameState>(0); }

  V<Object> data_argument() const { return input<Object>(1); }

  V<Context> context() const { return input<Context>(2); }

  base::Vector<const OpIndex> arguments() const {
    return inputs().SubVector(kNumNonParamInputs, inputs().size());
  }

  FastApiCallOp(V<LazyFrameState> frame_state, V<Object> data_argument,
                V<Context> context, base::Vector<const OpIndex> arguments,
                const FastApiCallParameters* parameters,
                base::Vector<const RegisterRepresentation> out_reps)
      : Base(kNumNonParamInputs + arguments.size()),
        parameters(parameters),
        out_reps(out_reps),
        lazy_deopt_on_throw(LazyDeoptOnThrow{false}) {
    base::Vector<OpIndex> inputs = this->inputs();
    inputs[0] = frame_state;
    inputs[1] = data_argument;
    inputs[2] = context;
    inputs.SubVector(kNumNonParamInputs, kNumNonParamInputs + arguments.size())
        .OverwriteWith(arguments);
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    V<LazyFrameState> mapped_frame_state = mapper.Map(frame_state());
    OpIndex mapped_data_argument = mapper.Map(data_argument());
    V<Context> mapped_context = mapper.Map(context());
    auto mapped_arguments = mapper.template Map<8>(arguments());
    return fn(mapped_frame_state, mapped_data_argument, mapped_context,
              base::VectorOf(mapped_arguments), parameters, out_reps);
  }

  static FastApiCallOp& New(
      Graph* graph, V<LazyFrameState> frame_state, V<Object> data_argument,
      V<Context> context, base::Vector<const OpIndex> arguments,
      const FastApiCallParameters* parameters,
      base::Vector<const RegisterRepresentation> out_reps) {
    return Base::New(graph, kNumNonParamInputs + arguments.size(), frame_state,
                     data_argument, context, arguments, parameters, out_reps);
  }

  // out_reps[0] is always word32.
  auto options() const {
    DCHECK_EQ(out_reps[0], RegisterRepresentation::Word32());
    return std::tuple{parameters, out_reps[1], lazy_deopt_on_throw};
  }
};

struct RuntimeAbortOp : FixedArityOperationT<0, RuntimeAbortOp> {
  AbortReason reason;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  explicit RuntimeAbortOp(AbortReason reason) : reason(reason) {}


  auto options() const { return std::tuple{reason}; }
};

struct EnsureWritableFastElementsOp
    : FixedArityOperationT<2, EnsureWritableFastElementsOp> {
  // TODO(tebbi): Can we have more precise effects here?
  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  OpIndex object() const { return Base::input(0); }
  OpIndex elements() const { return Base::input(1); }

  EnsureWritableFastElementsOp(OpIndex object, OpIndex elements)
      : Base(object, elements) {}


  auto options() const { return std::tuple{}; }
};

struct MaybeGrowFastElementsOp
    : FixedArityOperationT<5, MaybeGrowFastElementsOp> {
  GrowFastElementsMode mode;
  FeedbackSource feedback;

  // TODO(tebbi): Can we have more precise effects here?
  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Word32(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  V<Object> object() const { return Base::input<Object>(0); }
  V<Object> elements() const { return Base::input<Object>(1); }
  V<Word32> index() const { return Base::input<Word32>(2); }
  V<Word32> elements_length() const { return Base::input<Word32>(3); }
  V<EagerFrameState> frame_state() const {
    return Base::input<EagerFrameState>(4);
  }

  MaybeGrowFastElementsOp(V<Object> object, V<Object> elements, V<Word32> index,
                          V<Word32> elements_length,
                          V<EagerFrameState> frame_state,
                          GrowFastElementsMode mode,
                          const FeedbackSource& feedback)
      : Base(object, elements, index, elements_length, frame_state),
        mode(mode),
        feedback(feedback) {}

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple{mode, feedback}; }
};

struct TransitionElementsKindOp
    : FixedArityOperationT<1, TransitionElementsKindOp> {
  ElementsTransition transition;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  OpIndex object() const { return Base::input(0); }

  TransitionElementsKindOp(OpIndex object, const ElementsTransition& transition)
      : Base(object), transition(transition) {}


  auto options() const { return std::tuple{transition}; }
};

struct TransitionElementsKindOrCheckMapOp
    : FixedArityOperationT<3, TransitionElementsKindOrCheckMapOp> {
  ElementsTransitionWithMultipleSources transition;

  static constexpr OpEffects effects = OpEffects().CanCallAnything();
  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  V<HeapObject> object() const { return Base::input<HeapObject>(0); }

  TransitionElementsKindOrCheckMapOp(
      V<HeapObject> object, V<Map> map, V<EagerFrameState> frame_state,
      const ElementsTransitionWithMultipleSources& transition)
      : Base(object, map, frame_state), transition(transition) {}

  auto options() const { return std::tuple{transition}; }
};

struct FindOrderedHashEntryOp
    : FixedArityOperationT<2, FindOrderedHashEntryOp> {
  enum class Kind : uint8_t {
    kFindOrderedHashMapEntry,
    kFindOrderedHashMapEntryForInt32Key,
    kFindOrderedHashSetEntry,
  };
  Kind kind;

  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanReadMemory().CanAllocate();
  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (kind) {
      case Kind::kFindOrderedHashMapEntry:
      case Kind::kFindOrderedHashSetEntry:
        return RepVector<RegisterRepresentation::Tagged()>();
      case Kind::kFindOrderedHashMapEntryForInt32Key:
        return RepVector<RegisterRepresentation::WordPtr()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return kind == Kind::kFindOrderedHashMapEntryForInt32Key
               ? MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                                MaybeRegisterRepresentation::Word32()>()
               : MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                                MaybeRegisterRepresentation::Tagged()>();
  }

  OpIndex data_structure() const { return Base::input(0); }
  V<Word32> key() const { return Base::input<Word32>(1); }

  FindOrderedHashEntryOp(OpIndex data_structure, V<Word32> key, Kind kind)
      : Base(data_structure, key), kind(kind) {}


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           FindOrderedHashEntryOp::Kind kind);

struct WeakCollectionGetOp : FixedArityOperationT<2, WeakCollectionGetOp> {
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanReadMemory();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Tagged()>();
  }

  V<JSWeakCollection> receiver() const {
    return Base::input<JSWeakCollection>(0);
  }
  V<Object> key() const { return Base::input<Object>(1); }

  WeakCollectionGetOp(V<JSWeakCollection> receiver, V<Object> key)
      : Base(receiver, key) {}

  auto options() const { return std::tuple{}; }
};

struct CommentOp : FixedArityOperationT<0, CommentOp> {
  const char* message;

  // Comments should not be removed.
  static constexpr OpEffects effects = OpEffects().RequiredWhenUnused();

  explicit CommentOp(const char* message) : message(message) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  auto options() const { return std::tuple{message}; }
};

#if V8_ENABLE_WEBASSEMBLY

V8_EXPORT_PRIVATE const RegisterRepresentation& RepresentationFor(
    wasm::ValueType type);

struct GlobalGetOp : FixedArityOperationT<1, GlobalGetOp> {
  const wasm::WasmGlobal* global;
  static constexpr OpEffects effects = OpEffects().CanReadMemory();

  V<WasmTrustedInstanceData> instance() const {
    return input<WasmTrustedInstanceData>(0);
  }

  GlobalGetOp(V<WasmTrustedInstanceData> instance,
              const wasm::WasmGlobal* global)
      : Base(instance), global(global) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    const RegisterRepresentation& repr = RepresentationFor(global->type);
    return base::VectorOf(&repr, 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }


  auto options() const { return std::tuple{global}; }
};

struct WasmIncCoverageCounterOp
    : FixedArityOperationT<0, WasmIncCoverageCounterOp> {
  Address counter_addr;

  static constexpr OpEffects effects =
      OpEffects().CanReadOffHeapMemory().CanWriteOffHeapMemory();

  explicit WasmIncCoverageCounterOp(Address counter_addr)
      : Base(), counter_addr(counter_addr) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  auto options() const { return std::tuple{counter_addr}; }
  void PrintOptions(std::ostream& os) const;
};

struct GlobalSetOp : FixedArityOperationT<2, GlobalSetOp> {
  const wasm::WasmGlobal* global;
  static constexpr OpEffects effects = OpEffects().CanWriteMemory();

  V<WasmTrustedInstanceData> instance() const {
    return input<WasmTrustedInstanceData>(0);
  }
  V<Any> value() const { return input<Any>(1); }

  explicit GlobalSetOp(V<WasmTrustedInstanceData> instance, V<Any> value,
                       const wasm::WasmGlobal* global)
      : Base(instance, value), global(global) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    storage.resize(2);
    storage[0] = MaybeRegisterRepresentation::Tagged();
    storage[1] = MaybeRegisterRepresentation(RepresentationFor(global->type));
    return base::VectorOf(storage);
  }

  void Validate(const Graph& graph) const { DCHECK(global->mutability); }

  auto options() const { return std::tuple{global}; }
};

struct RootConstantOp : FixedArityOperationT<0, RootConstantOp> {
  RootIndex index;
  static constexpr OpEffects effects = OpEffects();

  explicit RootConstantOp(RootIndex index) : Base(), index(index) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  void Validate(const Graph& graph) const {}

  auto options() const { return std::tuple{index}; }
};

struct IsRootConstantOp : FixedArityOperationT<1, IsRootConstantOp> {
  RootIndex index;
  static constexpr OpEffects effects = OpEffects();

  V<Object> object() const { return input<Object>(0); }

  IsRootConstantOp(V<Object> object, RootIndex index)
      : Base(object), index(index) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{index}; }
};

struct NullOp : FixedArityOperationT<0, NullOp> {
  wasm::ValueType type;
  static constexpr OpEffects effects = OpEffects();

  explicit NullOp(wasm::ValueType type) : Base(), type(type) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  void Validate(const Graph& graph) const {
    DCHECK(type.is_ref() && type.is_nullable());
  }

  auto options() const { return std::tuple{type}; }
};

struct IsNullOp : FixedArityOperationT<1, IsNullOp> {
  wasm::ValueType type;
  static constexpr OpEffects effects = OpEffects();

  V<Object> object() const { return input<Object>(0); }

  IsNullOp(V<Object> object, wasm::ValueType type) : Base(object), type(type) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{type}; }
};

// Traps on a null input, otherwise returns the input, type-cast to the
// respective non-nullable type.
struct AssertNotNullOp : OperationT<AssertNotNullOp> {
  wasm::ValueType type;
  TrapId trap_id;

  // Lowers to a trap and inherits {TrapIf}'s effects.
  static constexpr OpEffects effects =
      OpEffects().CanDependOnChecks().CanLeaveCurrentFunction();

  V<Object> object() const { return input<Object>(0); }
  OptionalV<EagerFrameState> frame_state() const {
    return input_count > 1 ? input<EagerFrameState>(1)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  AssertNotNullOp(V<Object> object, OptionalV<EagerFrameState> frame_state,
                  wasm::ValueType type, TrapId trap_id)
      : Base(1 + frame_state.valid()), type(type), trap_id(trap_id) {
    input(0) = object;
    if (frame_state.valid()) {
      input(1) = frame_state.value();
    }
  }

  static AssertNotNullOp& New(Graph* graph, V<Object> object,
                              OptionalV<EagerFrameState> frame_state,
                              wasm::ValueType type, TrapId trap_id) {
    return Base::New(graph, 1 + frame_state.valid(), object, frame_state, type,
                     trap_id);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  void Validate(const Graph& graph) const {
    // TODO(14108): Validate.
  }

  auto options() const { return std::tuple{type, trap_id}; }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(object()), mapper.Map(frame_state()), type, trap_id);
  }
};

// The runtime type (RTT) is a value representing a concrete type (in this case
// heap-type). The canonical RTTs are implicitly created values and invisible to
// the user in wasm-gc MVP. (See
// https://github.com/WebAssembly/gc/blob/main/proposals/gc/MVP.md#runtime-types)
struct RttCanonOp : FixedArityOperationT<1, RttCanonOp> {
  wasm::ModuleTypeIndex type_index;

  static constexpr OpEffects effects = OpEffects();

  explicit RttCanonOp(V<FixedArray> rtts, wasm::ModuleTypeIndex type_index)
      : Base(rtts), type_index(type_index) {}

  V<FixedArray> rtts() const { return input<FixedArray>(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{type_index}; }
};

struct WasmTypeCheckOp : OperationT<WasmTypeCheckOp> {
  WasmTypeCheckConfig config;

  static constexpr OpEffects effects = OpEffects().AssumesConsistentHeap();

  WasmTypeCheckOp(V<Object> object, OptionalV<Map> rtt,
                  WasmTypeCheckConfig config)
      : Base(1 + rtt.valid()), config(config) {
    input(0) = object;
    if (rtt.valid()) {
      input(1) = rtt.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(object()), mapper.Map(rtt()), config);
  }

  V<Object> object() const { return Base::input<Object>(0); }
  OptionalV<Map> rtt() const {
    return input_count > 1 ? input<Map>(1) : OptionalV<Map>::Nullopt();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return input_count > 1
               ? MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                                MaybeRegisterRepresentation::Tagged()>()
               : MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }


  auto options() const { return std::tuple{config}; }

  static WasmTypeCheckOp& New(Graph* graph, V<Object> object,
                              OptionalV<Map> rtt, WasmTypeCheckConfig config) {
    return Base::New(graph, 1 + rtt.valid(), object, rtt, config);
  }

  void Validate(const Graph& graph) const { DCHECK(config.is_valid()); }
};

struct WasmTypeCastOp : OperationT<WasmTypeCastOp> {
  WasmTypeCheckConfig config;
  bool has_rtt;
  bool has_frame_state;

  static constexpr OpEffects effects =
      OpEffects().CanLeaveCurrentFunction().CanDependOnChecks();

  WasmTypeCastOp(V<Object> object, OptionalV<Map> rtt,
                 OptionalV<EagerFrameState> frame_state,
                 WasmTypeCheckConfig config)
      : Base(1 + rtt.valid() + frame_state.valid()),
        config(config),
        has_rtt(rtt.valid()),
        has_frame_state(frame_state.valid()) {
    input(0) = object;
    int idx = 1;
    if (has_rtt) {
      input(idx++) = rtt.value();
    }
    if (has_frame_state) {
      input(idx++) = frame_state.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(object()), mapper.Map(rtt()),
              mapper.Map(frame_state()), config);
  }

  V<Object> object() const { return Base::input<Object>(0); }
  OptionalV<Map> rtt() const {
    return has_rtt ? input<Map>(1) : OptionalV<Map>::Nullopt();
  }
  OptionalV<EagerFrameState> frame_state() const {
    int idx = 1 + (has_rtt ? 1 : 0);
    return has_frame_state ? input<EagerFrameState>(idx)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return has_rtt ? MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                                    MaybeRegisterRepresentation::Tagged()>()
                   : MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{config}; }

  static WasmTypeCastOp& New(Graph* graph, V<Object> object, OptionalV<Map> rtt,
                             OptionalV<EagerFrameState> frame_state,
                             WasmTypeCheckConfig config) {
    return Base::New(graph, 1 + rtt.valid() + frame_state.valid(), object, rtt,
                     frame_state, config);
  }

  void Validate(const Graph& graph) const { DCHECK(config.is_valid()); }
};

// Annotate a value with a wasm type.
// This is a helper operation to propagate type information from the graph
// builder to type-based optimizations and will then be removed.
struct WasmTypeAnnotationOp : FixedArityOperationT<1, WasmTypeAnnotationOp> {
  static constexpr OpEffects effects = OpEffects().CanDependOnChecks();
  wasm::ValueType type;

  explicit WasmTypeAnnotationOp(V<Object> value, wasm::ValueType type)
      : Base(value), type(type) {}

  V<Object> value() const { return Base::input<Object>(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  void Validate(const Graph& graph) const {
    // In theory, the operation could be used for non-reference types as well.
    // This would require updating inputs_rep and outputs_rep to be based on
    // the wasm type.
    DCHECK(type.is_ref());
  }

  auto options() const { return std::tuple(type); }
};

// Identity operation that carries a pre-call FrameState for JS-to-Wasm wrapper
// inlining. Emitted before the Call for each numeric wasm argument so the
// wasm-in-js-inlining reducer can extract the FrameState and pass it to
// parameter conversion builtins, allowing correct lazy deoptimization when a
// JSReceiver triggers valueOf (crbug.com/493307329).
struct ProcessWasmArgumentOp : FixedArityOperationT<2, ProcessWasmArgumentOp> {
  // Pure identity operation: carries a frame state for the reducer to extract,
  // but has no runtime effects. Gets dead-code eliminated after the reducer
  // unwraps it.
  static constexpr OpEffects effects = OpEffects();

  ProcessWasmArgumentOp(V<Object> value, V<EagerFrameState> frame_state)
      : Base(value, frame_state) {}

  V<Object> value() const { return input<Object>(0); }
  V<EagerFrameState> frame_state() const { return input<EagerFrameState>(1); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  void Validate(const Graph& graph) const {
    DCHECK(Get(graph, frame_state()).Is<FrameStateOp>());
  }

  auto options() const { return std::tuple<>(); }
};

struct AnyConvertExternOp : FixedArityOperationT<1, AnyConvertExternOp> {
  SharedFlag is_shared;
  bool is_nullable;

  static constexpr OpEffects effects =
      SmiValuesAre31Bits()
          ? OpEffects().CanReadMemory()
          : OpEffects().CanReadMemory().CanAllocateWithoutIdentity();

  explicit AnyConvertExternOp(V<Object> object, SharedFlag is_shared,
                              bool is_nullable)
      : Base(object), is_shared(is_shared), is_nullable(is_nullable) {}

  void PrintOptions(std::ostream& os) const {
    os << "[" << is_shared << ", "
       << (is_nullable ? "nullable" : "non-nullable") << "]";
  }

  V<Object> object() const { return Base::input<Object>(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple(is_shared, is_nullable); }
};

struct ExternConvertAnyOp : FixedArityOperationT<1, ExternConvertAnyOp> {
  bool is_nullable;

  static constexpr OpEffects effects = OpEffects();

  explicit ExternConvertAnyOp(V<Object> object, bool is_nullable)
      : Base(object), is_nullable(is_nullable) {}

  void PrintOptions(std::ostream& os) const {
    os << "[" << (is_nullable ? "nullable" : "non-nullable") << "]";
  }

  V<Object> object() const { return Base::input<Object>(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple(is_nullable); }
};

struct StructGetOp : OperationT<StructGetOp> {
  // We represent `ref.get_desc` as a special form of StructGetOp, because
  // the concept is so similar: have an object, load a value from it.
  static constexpr int kDescFieldIndex = -1;

  bool is_signed;  // `false` only for unsigned i8/i16 accesses.
  CheckForNull null_check;
  const wasm::StructType* type;
  wasm::ModuleTypeIndex type_index;
  int field_index;
  std::optional<AtomicMemoryOrder> memory_order;

  OpEffects Effects() const {
    OpEffects result =
        OpEffects()
            // This should not float above a protective null check.
            .CanDependOnChecks()
            .CanReadMemory();
    if (null_check == kWithNullCheck) {
      // This may trap.
      result = result.CanLeaveCurrentFunction();
    }
    if (memory_order.has_value()) {
      // Pretending that the op can write prevents reordering.
      result = result.CanWriteMemory();
    }
    return result;
  }

  StructGetOp(V<WasmStructNullable> object,
              OptionalV<EagerFrameState> frame_state,
              const wasm::StructType* type, wasm::ModuleTypeIndex type_index,
              int field_index, bool is_signed, CheckForNull null_check,
              std::optional<AtomicMemoryOrder> memory_order)
      : Base(1 + frame_state.valid()),
        is_signed(is_signed),
        null_check(null_check),
        type(type),
        type_index(type_index),
        field_index(field_index),
        memory_order(memory_order) {
    input(0) = object;
    if (frame_state.valid()) {
      input(1) = frame_state.value();
    }
  }

  static StructGetOp& New(Graph* graph, V<WasmStructNullable> object,
                          OptionalV<EagerFrameState> frame_state,
                          const wasm::StructType* type,
                          wasm::ModuleTypeIndex type_index, int field_index,
                          bool is_signed, CheckForNull null_check,
                          std::optional<AtomicMemoryOrder> memory_order) {
    return Base::New(graph, 1 + frame_state.valid(), object, frame_state, type,
                     type_index, field_index, is_signed, null_check,
                     memory_order);
  }

  V<WasmStructNullable> object() const { return input<WasmStructNullable>(0); }
  OptionalV<EagerFrameState> frame_state() const {
    return input_count > 1 ? input<EagerFrameState>(1)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  bool is_atomic() const { return memory_order.has_value(); }
  bool is_get_desc() const { return field_index == kDescFieldIndex; }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    if (is_get_desc()) {
      static constexpr RegisterRepresentation kTagged[] = {
          RegisterRepresentation::Tagged()};
      return base::VectorOf(kTagged);
    }
    return base::VectorOf(&RepresentationFor(type->field(field_index)), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  void Validate(const Graph& graph) const {
    if (is_get_desc()) {
      DCHECK(is_signed);
      return;
    }
    DCHECK_LE(0, field_index);
    DCHECK_LT(field_index, type->field_count());
    DCHECK_IMPLIES(!is_signed, type->field(field_index).is_packed());
  }

  auto options() const {
    return std::tuple{type,      type_index, field_index,
                      is_signed, null_check, memory_order};
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(object()), mapper.Map(frame_state()), type, type_index,
              field_index, is_signed, null_check, memory_order);
  }

  void PrintOptions(std::ostream& os) const;
};

struct StructSetOp : OperationT<StructSetOp> {
  // Initialization has stricter OpEffects limiting e.g. the rescheduling of the
  // operation.
  enum class Kind : uint8_t { kInitialize, kAssign };
  // TODO(jkummerow): If we stored the ValueType here, that would save a few
  // lookups later.
  wasm::ModuleTypeIndex type_index;
  const wasm::StructType* type;
  int field_index;
  std::optional<AtomicMemoryOrder> memory_order;
  WriteBarrierKind write_barrier;
  CheckForNull null_check : 1;
  Kind kind : 1;

  OpEffects Effects() const {
    OpEffects result =
        OpEffects()
            // This should not float above a protective null check.
            .CanDependOnChecks()
            .CanWriteMemory();
    if (kind == Kind::kInitialize) {
      result = result.CanDoRawHeapAccess();
    }
    if (null_check == kWithNullCheck) {
      // This may trap.
      result = result.CanLeaveCurrentFunction();
    }
    return result;
  }

  StructSetOp(V<WasmStructNullable> object, V<Any> value,
              OptionalV<EagerFrameState> frame_state,
              const wasm::StructType* type, wasm::ModuleTypeIndex type_index,
              int field_index, CheckForNull null_check,
              std::optional<AtomicMemoryOrder> memory_order,
              WriteBarrierKind write_barrier, Kind kind)
      : Base(2 + frame_state.valid()),
        type_index(type_index),
        type(type),
        field_index(field_index),
        memory_order(memory_order),
        write_barrier(write_barrier),
        null_check(null_check),
        kind(kind) {
    input(0) = object;
    input(1) = value;
    if (frame_state.valid()) {
      input(2) = frame_state.value();
    }
  }

  static StructSetOp& New(Graph* graph, V<WasmStructNullable> object,
                          V<Any> value, OptionalV<EagerFrameState> frame_state,
                          const wasm::StructType* type,
                          wasm::ModuleTypeIndex type_index, int field_index,
                          CheckForNull null_check,
                          std::optional<AtomicMemoryOrder> memory_order,
                          WriteBarrierKind write_barrier, Kind kind) {
    return Base::New(graph, 2 + frame_state.valid(), object, value, frame_state,
                     type, type_index, field_index, null_check, memory_order,
                     write_barrier, kind);
  }

  V<WasmStructNullable> object() const { return input<WasmStructNullable>(0); }
  V<Any> value() const { return input(1); }
  OptionalV<EagerFrameState> frame_state() const {
    return input_count > 2 ? input<EagerFrameState>(2)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    storage.resize(2);
    storage[0] = RegisterRepresentation::Tagged();
    storage[1] = RepresentationFor(type->field(field_index));
    return base::VectorOf(storage);
  }

  void Validate(const Graph& graph) const {
    DCHECK_LT(field_index, type->field_count());
  }

  auto options() const {
    return std::tuple{type,         type_index,    field_index, null_check,
                      memory_order, write_barrier, kind};
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(object()), mapper.Map(value()),
              mapper.Map(frame_state()), type, type_index, field_index,
              null_check, memory_order, write_barrier, kind);
  }

  void PrintOptions(std::ostream& os) const;
};

struct StructAtomicRMWOp : OperationT<StructAtomicRMWOp> {
  using BinOp = AtomicRMWOp::BinOp;
  BinOp bin_op;
  CheckForNull null_check;
  const wasm::StructType* type;
  wasm::ModuleTypeIndex type_index;
  int field_index;
  AtomicMemoryOrder memory_order;

  OpEffects Effects() const {
    OpEffects result =
        OpEffects()
            // This should not float above a protective null check.
            .CanDependOnChecks()
            .CanReadMemory()
            .CanWriteMemory();
    if (null_check == kWithNullCheck) {
      // This may trap.
      result = result.CanLeaveCurrentFunction();
    }
    return result;
  }

  StructAtomicRMWOp(V<WasmStructNullable> object, OpIndex value,
                    OptionalOpIndex expected, BinOp bin_op,
                    const wasm::StructType* type,
                    wasm::ModuleTypeIndex type_index, int field_index,
                    CheckForNull null_check, AtomicMemoryOrder memory_order)
      : Base(2 + expected.valid()),
        bin_op(bin_op),
        null_check(null_check),
        type(type),
        type_index(type_index),
        field_index(field_index),
        memory_order(memory_order) {
    input(0) = object;
    input(1) = value;
    if (expected.valid()) {
      input(2) = expected.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(object()), mapper.Map(value()), mapper.Map(expected()),
              bin_op, type, type_index, field_index, null_check, memory_order);
  }

  static StructAtomicRMWOp& New(Graph* graph, V<WasmStructNullable> object,
                                V<Any> value, OptionalOpIndex expected,
                                BinOp bin_op, const wasm::StructType* type,
                                wasm::ModuleTypeIndex type_index,
                                int field_index, CheckForNull null_check,
                                AtomicMemoryOrder memory_order) {
    return Base::New(graph, 2 + expected.valid(), object, value, expected,
                     bin_op, type, type_index, field_index, null_check,
                     memory_order);
  }

  V<WasmStructNullable> object() const { return input<WasmStructNullable>(0); }
  V<Any> value() const { return input(1); }
  OptionalOpIndex expected() const {
    return input_count == 3 ? input(2) : OpIndex::Invalid();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&RepresentationFor(type->field(field_index)), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    if (bin_op == BinOp::kCompareExchange) {
      return InitVectorOf(storage,
                          {RegisterRepresentation::Tagged(),
                           RepresentationFor(type->field(field_index)),
                           RepresentationFor(type->field(field_index))});
    }
    return InitVectorOf(storage, {RegisterRepresentation::Tagged(),
                                  RepresentationFor(type->field(field_index))});
  }

  void Validate(const Graph& graph) const {
    DCHECK_LT(field_index, type->field_count());
    DCHECK(type->field(field_index) == wasm::kWasmI32 ||
           type->field(field_index) == wasm::kWasmI64 ||
           (type->field(field_index).is_ref() &&
            (bin_op == BinOp::kExchange || bin_op == BinOp::kCompareExchange)));
    DCHECK_EQ(bin_op == BinOp::kCompareExchange, expected().valid());
  }

  auto options() const {
    return std::tuple{bin_op,      type,       type_index,
                      field_index, null_check, memory_order};
  }
};

struct ArrayGetOp : FixedArityOperationT<2, ArrayGetOp> {
  bool is_signed;
  const wasm::ArrayType* array_type;
  std::optional<AtomicMemoryOrder> memory_order;

  // ArrayGetOp may never trap as it is always protected by a length check.
  OpEffects Effects() const {
    OpEffects result =
        OpEffects()
            // This should not float above a protective null/length check.
            .CanDependOnChecks()
            .CanReadMemory();
    if (memory_order.has_value()) {
      // Pretending that the op can write prevents reordering / GVN.
      result = result.CanWriteMemory();
    }
    return result;
  }

  ArrayGetOp(V<WasmArrayNullable> array, V<Word32> index,
             const wasm::ArrayType* array_type, bool is_signed,
             std::optional<AtomicMemoryOrder> memory_order)
      : Base(array, index),
        is_signed(is_signed),
        array_type(array_type),
        memory_order(memory_order) {}

  V<WasmArrayNullable> array() const { return input<WasmArrayNullable>(0); }
  V<Word32> index() const { return input<Word32>(1); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&RepresentationFor(array_type->element_type()), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  auto options() const {
    return std::tuple{array_type, is_signed, memory_order};
  }
  void PrintOptions(std::ostream& os) const;
};

struct ArraySetOp : FixedArityOperationT<3, ArraySetOp> {
  // Initialization has stricter OpEffects limiting e.g. the rescheduling of the
  // operation.
  enum class Kind { kInitialize, kAssign };
  wasm::ValueType element_type;
  std::optional<AtomicMemoryOrder> memory_order;
  WriteBarrierKind write_barrier;
  Kind kind;

  OpEffects Effects() const {
    OpEffects result =
        OpEffects()
            // This should not float above a protective null/length check.
            .CanDependOnChecks()
            .CanWriteMemory();
    if (kind == Kind::kInitialize) {
      result = result.CanDoRawHeapAccess();
    }
    return result;
  }

  ArraySetOp(V<WasmArrayNullable> array, V<Word32> index, V<Any> value,
             wasm::ValueType element_type,
             std::optional<AtomicMemoryOrder> memory_order,
             WriteBarrierKind write_barrier, Kind kind)
      : Base(array, index, value),
        element_type(element_type),
        memory_order(memory_order),
        write_barrier(write_barrier),
        kind(kind) {}

  V<WasmArrayNullable> array() const { return input<WasmArrayNullable>(0); }
  V<Word32> index() const { return input<Word32>(1); }
  V<Any> value() const { return input<Any>(2); }

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(storage, {RegisterRepresentation::Tagged(),
                                  RegisterRepresentation::Word32(),
                                  RepresentationFor(element_type)});
  }

  auto options() const {
    return std::tuple{element_type, memory_order, write_barrier, kind};
  }
  void PrintOptions(std::ostream& os) const;
};

struct ArrayAtomicRMWOp : OperationT<ArrayAtomicRMWOp> {
  using BinOp = AtomicRMWOp::BinOp;
  BinOp bin_op;
  wasm::ValueType element_type;
  AtomicMemoryOrder memory_order;

  OpEffects Effects() const {
    return OpEffects()
        // This should not float above a protective null check.
        .CanDependOnChecks()
        .CanReadMemory()
        .CanWriteMemory();
  }

  ArrayAtomicRMWOp(V<WasmArrayNullable> array, V<Word32> index, OpIndex value,
                   OptionalV<Any> expected, BinOp bin_op,
                   wasm::ValueType element_type, AtomicMemoryOrder memory_order)
      : Base(3 + expected.valid()),
        bin_op(bin_op),
        element_type(element_type),
        memory_order(memory_order) {
    input(0) = array;
    input(1) = index;
    input(2) = value;
    if (expected.valid()) {
      input(3) = expected.value();
    }
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(array()), mapper.Map(index()), mapper.Map(value()),
              mapper.Map(expected()), bin_op, element_type, memory_order);
  }

  static ArrayAtomicRMWOp& New(Graph* graph, V<WasmArrayNullable> array,
                               V<Word32> index, OpIndex value,
                               OptionalV<Any> expected, BinOp bin_op,
                               wasm::ValueType element_type,
                               AtomicMemoryOrder memory_order) {
    return Base::New(graph, 3 + expected.valid(), array, index, value, expected,
                     bin_op, element_type, memory_order);
  }

  V<WasmArrayNullable> array() const { return input<WasmArrayNullable>(0); }
  V<Word32> index() const { return input<Word32>(1); }
  V<Any> value() const { return input<Any>(2); }
  OptionalV<Any> expected() const {
    return input_count == 4 ? input(3) : OpIndex::Invalid();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return base::VectorOf(&RepresentationFor(element_type), 1);
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    if (bin_op == BinOp::kCompareExchange) {
      return InitVectorOf(
          storage,
          {RegisterRepresentation::Tagged(), RegisterRepresentation::Word32(),
           RepresentationFor(element_type), RepresentationFor(element_type)});
    }
    return InitVectorOf(storage, {RegisterRepresentation::Tagged(),
                                  RegisterRepresentation::Word32(),
                                  RepresentationFor(element_type)});
  }

  void Validate(const Graph& graph) const {
    DCHECK(element_type == wasm::kWasmI32 || element_type == wasm::kWasmI64 ||
           (element_type.is_ref() &&
            (bin_op == BinOp::kExchange || bin_op == BinOp::kCompareExchange)));
    DCHECK_EQ(bin_op == BinOp::kCompareExchange, expected().valid());
  }

  auto options() const {
    return std::tuple{bin_op, element_type, memory_order};
  }
};

struct ArrayLengthOp : OperationT<ArrayLengthOp> {
  CheckForNull null_check;

  OpEffects Effects() const {
    OpEffects result =
        OpEffects()
            // This should not float above a protective null check.
            .CanDependOnChecks()
            .CanReadMemory();
    if (null_check == kWithNullCheck) {
      // This may trap.
      result = result.CanLeaveCurrentFunction();
    }
    return result;
  }

  explicit ArrayLengthOp(V<WasmArrayNullable> array,
                         OptionalV<EagerFrameState> frame_state,
                         CheckForNull null_check)
      : Base(1 + frame_state.valid()), null_check(null_check) {
    input(0) = array;
    if (frame_state.valid()) {
      input(1) = frame_state.value();
    }
  }

  V<WasmArrayNullable> array() const { return input<WasmArrayNullable>(0); }
  OptionalV<EagerFrameState> frame_state() const {
    return input_count > 1 ? input<EagerFrameState>(1)
                           : OptionalV<EagerFrameState>::Nullopt();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Tagged()>();
  }

  template <typename Fn, typename Mapper>
  V8_INLINE auto Explode(Fn fn, Mapper& mapper) const {
    return fn(mapper.Map(array()), mapper.Map(frame_state()), null_check);
  }

  static ArrayLengthOp& New(Graph* graph, V<WasmArrayNullable> array,
                            OptionalV<EagerFrameState> frame_state,
                            CheckForNull null_check) {
    return Base::New(graph, 1 + frame_state.valid(), array, frame_state,
                     null_check);
  }

  auto options() const { return std::tuple{null_check}; }
};

struct WasmAllocateArrayOp : FixedArityOperationT<2, WasmAllocateArrayOp> {
  static constexpr OpEffects effects =
      OpEffects().CanAllocate().CanDoRawHeapAccess().CanLeaveCurrentFunction();

  const wasm::ArrayType* array_type;
  SharedFlag is_shared;

  explicit WasmAllocateArrayOp(V<Map> rtt, V<Word32> length,
                               const wasm::ArrayType* array_type,
                               SharedFlag is_shared)
      : Base(rtt, length), array_type(array_type), is_shared(is_shared) {}

  V<Map> rtt() const { return Base::input<Map>(0); }
  V<Word32> length() const { return Base::input<Word32>(1); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged(),
                          MaybeRegisterRepresentation::Word32()>();
  }

  auto options() const { return std::tuple{array_type, is_shared}; }
  void PrintOptions(std::ostream& os) const;
};

struct WasmAllocateStructOp : FixedArityOperationT<1, WasmAllocateStructOp> {
  static constexpr OpEffects effects =
      OpEffects().CanAllocate().CanDoRawHeapAccess().CanLeaveCurrentFunction();

  const wasm::StructType* struct_type;
  wasm::ModuleTypeIndex type_index;

  explicit WasmAllocateStructOp(V<Map> rtt, const wasm::StructType* struct_type,
                                wasm::ModuleTypeIndex type_index)
      : Base(rtt), struct_type(struct_type), type_index(type_index) {}

  V<Map> rtt() const { return Base::input<Map>(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{struct_type, type_index}; }
  void PrintOptions(std::ostream& os) const;
};

struct WasmRefFuncOp : FixedArityOperationT<1, WasmRefFuncOp> {
  static constexpr OpEffects effects = OpEffects().CanAllocate();
  uint32_t function_index;

  explicit WasmRefFuncOp(V<WasmTrustedInstanceData> wasm_instance,
                         uint32_t function_index)
      : Base(wasm_instance), function_index(function_index) {}

  V<WasmTrustedInstanceData> instance() const {
    return input<WasmTrustedInstanceData>(0);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{function_index}; }
};

// Casts a JavaScript string to a flattened wtf16 string.
// TODO(14108): Can we optimize stringref operations without adding this as a
// special operations?
struct StringAsWtf16Op : FixedArityOperationT<1, StringAsWtf16Op> {
  static constexpr OpEffects effects =
      OpEffects()
          // This should not float above a protective null/length check.
          .CanDependOnChecks()
          .CanReadMemory();

  explicit StringAsWtf16Op(V<String> string) : Base(string) {}

  V<String> string() const { return input<String>(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{}; }
};

// Takes a flattened string and extracts the first string pointer, the base
// offset and the character width shift.
struct StringPrepareForGetCodeUnitOp
    : FixedArityOperationT<1, StringPrepareForGetCodeUnitOp> {
  static constexpr OpEffects effects =
      OpEffects()
          // This should not float above a protective null/length check.
          .CanDependOnChecks()
          .CanReadMemory();

  explicit StringPrepareForGetCodeUnitOp(V<Object> string) : Base(string) {}

  OpIndex string() const { return input(0); }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged(),
                     RegisterRepresentation::WordPtr(),
                     RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Tagged()>();
  }

  auto options() const { return std::tuple{}; }
};

#endif  // V8_ENABLE_WEBASSEMBLY

#if V8_ENABLE_SIMD128

struct Simd128ConstantOp : FixedArityOperationT<0, Simd128ConstantOp> {
  static constexpr uint8_t kZero[kSimd128Size] = {};
  uint8_t value[kSimd128Size];

  static constexpr OpEffects effects = OpEffects();

  explicit Simd128ConstantOp(const uint8_t incoming_value[kSimd128Size])
      : Base() {
    std::copy(incoming_value, incoming_value + kSimd128Size, value);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  void Validate(const Graph& graph) const {
    // TODO(14108): Validate.
  }

  bool IsZero() const { return std::memcmp(kZero, value, kSimd128Size) == 0; }

  auto options() const { return std::tuple{value}; }
  void PrintOptions(std::ostream& os) const;
};

#define FOREACH_SIMD_128_BINARY_EXTENSION_OPCODE(V) \
  V(I16x8ExtMulLowI8x16S)                           \
  V(I16x8ExtMulHighI8x16S)                          \
  V(I16x8ExtMulLowI8x16U)                           \
  V(I16x8ExtMulHighI8x16U)                          \
  V(I32x4ExtMulLowI16x8S)                           \
  V(I32x4ExtMulHighI16x8S)                          \
  V(I32x4ExtMulLowI16x8U)                           \
  V(I32x4ExtMulHighI16x8U)                          \
  V(I64x2ExtMulLowI32x4S)                           \
  V(I64x2ExtMulHighI32x4S)                          \
  V(I64x2ExtMulLowI32x4U)                           \
  V(I64x2ExtMulHighI32x4U)

#define FOREACH_SIMD_128_BINARY_BASIC_OPCODE(V) \
  V(I8x16Eq)                                    \
  V(I8x16Ne)                                    \
  V(I8x16GtS)                                   \
  V(I8x16GtU)                                   \
  V(I8x16GeS)                                   \
  V(I8x16GeU)                                   \
  V(I16x8Eq)                                    \
  V(I16x8Ne)                                    \
  V(I16x8GtS)                                   \
  V(I16x8GtU)                                   \
  V(I16x8GeS)                                   \
  V(I16x8GeU)                                   \
  V(I32x4Eq)                                    \
  V(I32x4Ne)                                    \
  V(I32x4GtS)                                   \
  V(I32x4GtU)                                   \
  V(I32x4GeS)                                   \
  V(I32x4GeU)                                   \
  V(F32x4Eq)                                    \
  V(F32x4Ne)                                    \
  V(F32x4Lt)                                    \
  V(F32x4Le)                                    \
  V(F64x2Eq)                                    \
  V(F64x2Ne)                                    \
  V(F64x2Lt)                                    \
  V(F64x2Le)                                    \
  V(S128And)                                    \
  V(S128AndNot)                                 \
  V(S128Or)                                     \
  V(S128Xor)                                    \
  V(I8x16SConvertI16x8)                         \
  V(I8x16UConvertI16x8)                         \
  V(I8x16Add)                                   \
  V(I8x16AddSatS)                               \
  V(I8x16AddSatU)                               \
  V(I8x16Sub)                                   \
  V(I8x16SubSatS)                               \
  V(I8x16SubSatU)                               \
  V(I8x16MinS)                                  \
  V(I8x16MinU)                                  \
  V(I8x16MaxS)                                  \
  V(I8x16MaxU)                                  \
  V(I8x16RoundingAverageU)                      \
  V(I16x8Q15MulRSatS)                           \
  V(I16x8SConvertI32x4)                         \
  V(I16x8UConvertI32x4)                         \
  V(I16x8Add)                                   \
  V(I16x8AddSatS)                               \
  V(I16x8AddSatU)                               \
  V(I16x8Sub)                                   \
  V(I16x8SubSatS)                               \
  V(I16x8SubSatU)                               \
  V(I16x8Mul)                                   \
  V(I16x8MinS)                                  \
  V(I16x8MinU)                                  \
  V(I16x8MaxS)                                  \
  V(I16x8MaxU)                                  \
  V(I16x8RoundingAverageU)                      \
  V(I32x4Add)                                   \
  V(I32x4Sub)                                   \
  V(I32x4Mul)                                   \
  V(I32x4MinS)                                  \
  V(I32x4MinU)                                  \
  V(I32x4MaxS)                                  \
  V(I32x4MaxU)                                  \
  V(I32x4DotI16x8S)                             \
  V(I64x2Add)                                   \
  V(I64x2Sub)                                   \
  V(I64x2Mul)                                   \
  V(I64x2Eq)                                    \
  V(I64x2Ne)                                    \
  V(I64x2GtS)                                   \
  V(I64x2GeS)                                   \
  V(F32x4Add)                                   \
  V(F32x4Sub)                                   \
  V(F32x4Mul)                                   \
  V(F32x4Div)                                   \
  V(F32x4Min)                                   \
  V(F32x4Max)                                   \
  V(F32x4Pmin)                                  \
  V(F32x4Pmax)                                  \
  V(F64x2Add)                                   \
  V(F64x2Sub)                                   \
  V(F64x2Mul)                                   \
  V(F64x2Div)                                   \
  V(F64x2Min)                                   \
  V(F64x2Max)                                   \
  V(F64x2Pmin)                                  \
  V(F64x2Pmax)                                  \
  V(F32x4RelaxedMin)                            \
  V(F32x4RelaxedMax)                            \
  V(F64x2RelaxedMin)                            \
  V(F64x2RelaxedMax)                            \
  V(I16x8RelaxedQ15MulRS)                       \
  V(I16x8DotI8x16I7x16S)                        \
  FOREACH_SIMD_128_BINARY_EXTENSION_OPCODE(V)

#define FOREACH_SIMD_128_BINARY_SPECIAL_OPCODE(V) \
  V(I8x16Swizzle)                                 \
  V(I8x16RelaxedSwizzle)

#define FOREACH_SIMD_128_BINARY_MANDATORY_OPCODE(V) \
  FOREACH_SIMD_128_BINARY_BASIC_OPCODE(V)           \
  FOREACH_SIMD_128_BINARY_SPECIAL_OPCODE(V)

#define FOREACH_SIMD_128_BINARY_OPTIONAL_OPCODE(V) \
  V(F16x8Add)                                      \
  V(F16x8Sub)                                      \
  V(F16x8Mul)                                      \
  V(F16x8Div)                                      \
  V(F16x8Min)                                      \
  V(F16x8Max)                                      \
  V(F16x8Pmin)                                     \
  V(F16x8Pmax)                                     \
  V(F16x8Eq)                                       \
  V(F16x8Ne)                                       \
  V(F16x8Lt)                                       \
  V(F16x8Le)                                       \
  V(I32x4AddPairwise)                              \
  V(I32x4DotI8x16S)

#define FOREACH_SIMD_128_BINARY_OPCODE(V)     \
  FOREACH_SIMD_128_BINARY_MANDATORY_OPCODE(V) \
  FOREACH_SIMD_128_BINARY_OPTIONAL_OPCODE(V)

struct Simd128BinopOp : FixedArityOperationT<2, Simd128BinopOp> {
  // clang-format off
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_BINARY_OPCODE(DEFINE_KIND)
    kFirstExtensionOp = kI16x8ExtMulLowI8x16S,
    kLastExtensionOp = kI64x2ExtMulHighI32x4U,
#undef DEFINE_KIND
  };
  // clang-format on

  Kind kind;

  static bool IsCommutative(Kind kind) {
    switch (kind) {
      // TODO(14108): Explicitly list all commutative SIMD operations.
      case Kind::kI64x2Add:
      case Kind::kI32x4Add:
      case Kind::kI16x8Add:
      case Kind::kI8x16Add:
      case Kind::kF64x2Add:
      case Kind::kF32x4Add:

      case Kind::kI64x2Mul:
      case Kind::kI32x4Mul:
      case Kind::kI16x8Mul:
      case Kind::kF64x2Mul:
      case Kind::kF32x4Mul:

      case Kind::kS128Xor:

      case Kind::kI32x4AddPairwise:
        return true;
      default:
        return false;
    }
  }

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128(),
                          RegisterRepresentation::Simd128()>();
  }

  Simd128BinopOp(V<Simd128> left, V<Simd128> right, Kind kind)
      : Base(left, right), kind(kind) {}

  V<Simd128> left() const { return input<Simd128>(0); }
  V<Simd128> right() const { return input<Simd128>(1); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128BinopOp::Kind kind);

#define FOREACH_SIMD_128_UNARY_EXTENSION_OPCODE(V) \
  V(I16x8SConvertI8x16Low)                         \
  V(I16x8SConvertI8x16High)                        \
  V(I16x8UConvertI8x16Low)                         \
  V(I16x8UConvertI8x16High)                        \
  V(I32x4SConvertI16x8Low)                         \
  V(I32x4SConvertI16x8High)                        \
  V(I32x4UConvertI16x8Low)                         \
  V(I32x4UConvertI16x8High)                        \
  V(I64x2SConvertI32x4Low)                         \
  V(I64x2SConvertI32x4High)                        \
  V(I64x2UConvertI32x4Low)                         \
  V(I64x2UConvertI32x4High)

#define FOREACH_SIMD_128_UNARY_NON_OPTIONAL_OPCODE(V) \
  V(S128Not)                                          \
  V(F32x4DemoteF64x2Zero)                             \
  V(F64x2PromoteLowF32x4)                             \
  V(I8x16Abs)                                         \
  V(I8x16Neg)                                         \
  V(I8x16Popcnt)                                      \
  V(I16x8ExtAddPairwiseI8x16S)                        \
  V(I16x8ExtAddPairwiseI8x16U)                        \
  V(I32x4ExtAddPairwiseI16x8S)                        \
  V(I32x4ExtAddPairwiseI16x8U)                        \
  V(I16x8Abs)                                         \
  V(I16x8Neg)                                         \
  V(I32x4Abs)                                         \
  V(I32x4Neg)                                         \
  V(I64x2Abs)                                         \
  V(I64x2Neg)                                         \
  V(F32x4Abs)                                         \
  V(F32x4Neg)                                         \
  V(F32x4Sqrt)                                        \
  V(F64x2Abs)                                         \
  V(F64x2Neg)                                         \
  V(F64x2Sqrt)                                        \
  V(I32x4SConvertF32x4)                               \
  V(I32x4UConvertF32x4)                               \
  V(F32x4SConvertI32x4)                               \
  V(F32x4UConvertI32x4)                               \
  V(I32x4TruncSatF64x2SZero)                          \
  V(I32x4TruncSatF64x2UZero)                          \
  V(F64x2ConvertLowI32x4S)                            \
  V(F64x2ConvertLowI32x4U)                            \
  V(I32x4RelaxedTruncF32x4S)                          \
  V(I32x4RelaxedTruncF32x4U)                          \
  V(I32x4RelaxedTruncF64x2SZero)                      \
  V(I32x4RelaxedTruncF64x2UZero)                      \
  FOREACH_SIMD_128_UNARY_EXTENSION_OPCODE(V)

#define FOREACH_SIMD_128_UNARY_OPTIONAL_OPCODE(V)                             \
  V(F16x8Abs)                                                                 \
  V(F16x8Neg)                                                                 \
  V(F16x8Sqrt)                                                                \
  V(F16x8Ceil)                                                                \
  V(F16x8Floor)                                                               \
  V(F16x8Trunc)                                                               \
  V(F16x8NearestInt)                                                          \
  V(I16x8SConvertF16x8)                                                       \
  V(I16x8UConvertF16x8)                                                       \
  V(F16x8SConvertI16x8)                                                       \
  V(F16x8UConvertI16x8)                                                       \
  V(F16x8DemoteF32x4Zero)                                                     \
  V(F16x8DemoteF64x2Zero)                                                     \
  V(F32x4PromoteLowF16x8)                                                     \
  V(F32x4Ceil)                                                                \
  V(F32x4Floor)                                                               \
  V(F32x4Trunc)                                                               \
  V(F32x4NearestInt)                                                          \
  V(F64x2Ceil)                                                                \
  V(F64x2Floor)                                                               \
  V(F64x2Trunc)                                                               \
  V(F64x2NearestInt)                                                          \
  /* TODO(mliedtke): Rename to ReverseBytes once the naming is decoupled   */ \
  /* from Turbofan naming. */                                                 \
  V(Simd128ReverseBytes)

#define FOREACH_SIMD_128_UNARY_OPCODE(V)        \
  FOREACH_SIMD_128_UNARY_NON_OPTIONAL_OPCODE(V) \
  FOREACH_SIMD_128_UNARY_OPTIONAL_OPCODE(V)

struct Simd128UnaryOp : FixedArityOperationT<1, Simd128UnaryOp> {
  // clang-format off
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_UNARY_OPCODE(DEFINE_KIND)
    kFirstExtensionOp = kI16x8SConvertI8x16Low,
    kLastExtensionOp = kI64x2UConvertI32x4High,
#undef DEFINE_KIND
  };
  // clang-format on

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128()>();
  }

  Simd128UnaryOp(V<Simd128> input, Kind kind) : Base(input), kind(kind) {}

  V<Simd128> input() const { return Base::input<Simd128>(0); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128UnaryOp::Kind kind);

#define FOREACH_SIMD_128_REDUCE_OPTIONAL_OPCODE(V) \
  V(I8x16AddReduce)                                \
  V(I16x8AddReduce)                                \
  V(I32x4AddReduce)                                \
  V(I64x2AddReduce)                                \
  V(F32x4AddReduce)                                \
  V(F64x2AddReduce)

struct Simd128ReduceOp : FixedArityOperationT<1, Simd128ReduceOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_REDUCE_OPTIONAL_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128()>();
  }

  Simd128ReduceOp(V<Simd128> input, Kind kind) : Base(input), kind(kind) {}

  V<Simd128> input() const { return Base::input<Simd128>(0); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128ReduceOp::Kind kind);

#define FOREACH_SIMD_128_SHIFT_OPCODE(V) \
  V(I8x16Shl)                            \
  V(I8x16ShrS)                           \
  V(I8x16ShrU)                           \
  V(I16x8Shl)                            \
  V(I16x8ShrS)                           \
  V(I16x8ShrU)                           \
  V(I32x4Shl)                            \
  V(I32x4ShrS)                           \
  V(I32x4ShrU)                           \
  V(I64x2Shl)                            \
  V(I64x2ShrS)                           \
  V(I64x2ShrU)

struct Simd128ShiftOp : FixedArityOperationT<2, Simd128ShiftOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_SHIFT_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128(),
                          RegisterRepresentation::Word32()>();
  }

  Simd128ShiftOp(V<Simd128> input, V<Word32> shift, Kind kind)
      : Base(input, shift), kind(kind) {}

  V<Simd128> input() const { return Base::input<Simd128>(0); }
  V<Word32> shift() const { return Base::input<Word32>(1); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128ShiftOp::Kind kind);

#define FOREACH_SIMD_128_TEST_OPCODE(V) \
  V(V128AnyTrue)                        \
  V(I8x16AllTrue)                       \
  V(I8x16BitMask)                       \
  V(I16x8AllTrue)                       \
  V(I16x8BitMask)                       \
  V(I32x4AllTrue)                       \
  V(I32x4BitMask)                       \
  V(I64x2AllTrue)                       \
  V(I64x2BitMask)

struct Simd128TestOp : FixedArityOperationT<1, Simd128TestOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_TEST_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Word32()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128()>();
  }

  Simd128TestOp(V<Simd128> input, Kind kind) : Base(input), kind(kind) {}

  V<Simd128> input() const { return Base::input<Simd128>(0); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128TestOp::Kind kind);

#define FOREACH_SIMD_128_SPLAT_MANDATORY_OPCODE(V) \
  V(I8x16)                                         \
  V(I16x8)                                         \
  V(I32x4)                                         \
  V(I64x2)                                         \
  V(F32x4)                                         \
  V(F64x2)

#define FOREACH_SIMD_128_SPLAT_OPCODE(V)     \
  FOREACH_SIMD_128_SPLAT_MANDATORY_OPCODE(V) \
  V(F16x8)
struct Simd128SplatOp : FixedArityOperationT<1, Simd128SplatOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_SPLAT_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    switch (kind) {
      case Kind::kI8x16:
      case Kind::kI16x8:
      case Kind::kI32x4:
        return MaybeRepVector<RegisterRepresentation::Word32()>();
      case Kind::kI64x2:
        return MaybeRepVector<RegisterRepresentation::Word64()>();
      case Kind::kF16x8:
      case Kind::kF32x4:
        return MaybeRepVector<RegisterRepresentation::Float32()>();
      case Kind::kF64x2:
        return MaybeRepVector<RegisterRepresentation::Float64()>();
    }
    UNREACHABLE();
  }

  Simd128SplatOp(V<Any> input, Kind kind) : Base(input), kind(kind) {}

  V<Any> input() const { return Base::input<Any>(0); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128SplatOp::Kind kind);

#define FOREACH_SIMD_128_TERNARY_MASK_OPCODE(V) \
  V(S128Select)                                 \
  V(I8x16RelaxedLaneSelect)                     \
  V(I16x8RelaxedLaneSelect)                     \
  V(I32x4RelaxedLaneSelect)                     \
  V(I64x2RelaxedLaneSelect)

#define FOREACH_SIMD_128_TERNARY_OTHER_OPCODE(V) \
  V(F32x4Qfma)                                   \
  V(F32x4Qfms)                                   \
  V(F64x2Qfma)                                   \
  V(F64x2Qfms)                                   \
  V(I32x4DotI8x16I7x16AddS)

#define FOREACH_SIMD_128_TERNARY_OPTIONAL_OPCODE(V) \
  V(F16x8Qfma)                                      \
  V(F16x8Qfms)

#define FOREACH_SIMD_128_TERNARY_OPCODE(V) \
  FOREACH_SIMD_128_TERNARY_MASK_OPCODE(V)  \
  FOREACH_SIMD_128_TERNARY_OTHER_OPCODE(V) \
  FOREACH_SIMD_128_TERNARY_OPTIONAL_OPCODE(V)

struct Simd128TernaryOp : FixedArityOperationT<3, Simd128TernaryOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_TERNARY_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128(),
                          RegisterRepresentation::Simd128(),
                          RegisterRepresentation::Simd128()>();
  }

  Simd128TernaryOp(V<Simd128> first, V<Simd128> second, V<Simd128> third,
                   Kind kind)
      : Base(first, second, third), kind(kind) {}

  V<Simd128> first() const { return input<Simd128>(0); }
  V<Simd128> second() const { return input<Simd128>(1); }
  V<Simd128> third() const { return input<Simd128>(2); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd128TernaryOp::Kind kind);

struct Simd128ExtractLaneOp : FixedArityOperationT<1, Simd128ExtractLaneOp> {
  enum class Kind : uint8_t {
    kI8x16S,
    kI8x16U,
    kI16x8S,
    kI16x8U,
    kI32x4,
    kI64x2,
    kF16x8,
    kF32x4,
    kF64x2,
  };

  Kind kind;
  uint8_t lane;

  static constexpr OpEffects effects = OpEffects();

  static MachineRepresentation element_rep(Kind kind) {
    switch (kind) {
      case Kind::kI8x16S:
      case Kind::kI8x16U:
        return MachineRepresentation::kWord8;
      case Kind::kI16x8S:
      case Kind::kI16x8U:
        return MachineRepresentation::kWord16;
      case Kind::kI32x4:
        return MachineRepresentation::kWord32;
      case Kind::kI64x2:
        return MachineRepresentation::kWord64;
      case Kind::kF16x8:
      case Kind::kF32x4:
        return MachineRepresentation::kFloat32;
      case Kind::kF64x2:
        return MachineRepresentation::kFloat64;
    }
    UNREACHABLE();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    switch (kind) {
      case Kind::kI8x16S:
      case Kind::kI8x16U:
      case Kind::kI16x8S:
      case Kind::kI16x8U:
      case Kind::kI32x4:
        return RepVector<RegisterRepresentation::Word32()>();
      case Kind::kI64x2:
        return RepVector<RegisterRepresentation::Word64()>();
      case Kind::kF16x8:
      case Kind::kF32x4:
        return RepVector<RegisterRepresentation::Float32()>();
      case Kind::kF64x2:
        return RepVector<RegisterRepresentation::Float64()>();
    }
    UNREACHABLE();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128()>();
  }

  Simd128ExtractLaneOp(V<Simd128> input, Kind kind, uint8_t lane)
      : Base(input), kind(kind), lane(lane) {}

  V<Simd128> input() const { return Base::input<Simd128>(0); }

  void Validate(const Graph& graph) const {
#if DEBUG
    uint8_t lane_count;
    switch (kind) {
      case Kind::kI8x16S:
      case Kind::kI8x16U:
        lane_count = 16;
        break;
      case Kind::kI16x8S:
      case Kind::kI16x8U:
      case Kind::kF16x8:
        lane_count = 8;
        break;
      case Kind::kI32x4:
      case Kind::kF32x4:
        lane_count = 4;
        break;
      case Kind::kI64x2:
      case Kind::kF64x2:
        lane_count = 2;
        break;
    }
    DCHECK_LT(lane, lane_count);
#endif
  }

  auto options() const { return std::tuple{kind, lane}; }
  void PrintOptions(std::ostream& os) const;
};

struct Simd128ReplaceLaneOp : FixedArityOperationT<2, Simd128ReplaceLaneOp> {
  enum class Kind : uint8_t {
    kI8x16,
    kI16x8,
    kI32x4,
    kI64x2,
    kF16x8,
    kF32x4,
    kF64x2,
  };

  Kind kind;
  uint8_t lane;

  static constexpr OpEffects effects = OpEffects();

  static MachineRepresentation element_rep(Kind kind) {
    switch (kind) {
      case Kind::kI8x16:
        return MachineRepresentation::kWord8;
      case Kind::kI16x8:
        return MachineRepresentation::kWord16;
      case Kind::kI32x4:
        return MachineRepresentation::kWord32;
      case Kind::kI64x2:
        return MachineRepresentation::kWord64;
      case Kind::kF16x8:
      case Kind::kF32x4:
        return MachineRepresentation::kFloat32;
      case Kind::kF64x2:
        return MachineRepresentation::kFloat64;
    }
    UNREACHABLE();
  }

  static uint8_t lane_count(Kind kind) {
    switch (kind) {
      case Kind::kI8x16:
        return 16;
      case Kind::kI16x8:
      case Kind::kF16x8:
        return 8;
      case Kind::kI32x4:
      case Kind::kF32x4:
        return 4;
      case Kind::kI64x2:
      case Kind::kF64x2:
        return 2;
    }
    UNREACHABLE();
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }
  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(storage,
                        {RegisterRepresentation::Simd128(), new_lane_rep()});
  }

  Simd128ReplaceLaneOp(V<Simd128> into, V<Any> new_lane, Kind kind,
                       uint8_t lane)
      : Base(into, new_lane), kind(kind), lane(lane) {}

  V<Simd128> into() const { return input<Simd128>(0); }
  V<Any> new_lane() const { return input<Any>(1); }

  void Validate(const Graph& graph) const {
#if DEBUG
    uint8_t lane_count = Simd128ReplaceLaneOp::lane_count(kind);
    DCHECK_LT(lane, lane_count);
#endif
  }

  auto options() const { return std::tuple{kind, lane}; }
  void PrintOptions(std::ostream& os) const;

  RegisterRepresentation new_lane_rep() const {
    switch (kind) {
      case Kind::kI8x16:
      case Kind::kI16x8:
      case Kind::kI32x4:
        return RegisterRepresentation::Word32();
      case Kind::kI64x2:
        return RegisterRepresentation::Word64();
      case Kind::kF16x8:
      case Kind::kF32x4:
        return RegisterRepresentation::Float32();
      case Kind::kF64x2:
        return RegisterRepresentation::Float64();
    }
    UNREACHABLE();
  }
};

struct Simd128MoveLaneOp : FixedArityOperationT<2, Simd128MoveLaneOp> {
  using Kind = Simd128ReplaceLaneOp::Kind;

  const Kind kind;
  const uint8_t into_lane;
  const uint8_t from_lane;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }
  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return InitVectorOf(storage, {RegisterRepresentation::Simd128(),
                                  RegisterRepresentation::Simd128()});
  }

  Simd128MoveLaneOp(V<Simd128> into, V<Simd128> from, Kind kind,
                    uint8_t into_lane, uint8_t from_lane)
      : Base(into, from),
        kind(kind),
        into_lane(into_lane),
        from_lane(from_lane) {}

  V<Simd128> into() const { return input<Simd128>(0); }
  V<Simd128> from() const { return input<Simd128>(1); }

  void Validate(const Graph& graph) const {
    DCHECK_NE(kind, Kind::kF16x8);
    DCHECK_LT(into_lane, Simd128ReplaceLaneOp::lane_count(kind));
    DCHECK_LT(from_lane, Simd128ReplaceLaneOp::lane_count(kind));
  }

  auto options() const { return std::tuple{kind, into_lane, from_lane}; }
  void PrintOptions(std::ostream& os) const;
};

// If `mode` is `kLoad`, load a value from `base() + index() + offset`, whose
// size is determinded by `lane_kind`, and return the Simd128 `value()` with
// the lane specified by `lane_kind` and `lane` replaced with the loaded value.
// If `mode` is `kStore`, extract the lane specified by `lane` with size
// `lane_kind` from `value()`, and store it to `base() + index() + offset`.
struct Simd128LaneMemoryOp : FixedArityOperationT<3, Simd128LaneMemoryOp> {
  enum class Mode : bool { kLoad, kStore };
  using Kind = LoadOp::Kind;
  // The values encode the element_size_log2.
  enum class LaneKind : uint8_t { k8 = 0, k16 = 1, k32 = 2, k64 = 3 };

  Mode mode;
  Kind kind;
  LaneKind lane_kind;
  uint8_t lane;
  int offset;

  OpEffects Effects() const {
    OpEffects effects = mode == Mode::kLoad ? OpEffects().CanReadMemory()
                                            : OpEffects().CanWriteMemory();
    effects = effects.CanDependOnChecks();
    if (kind.with_trap_handler) effects = effects.CanLeaveCurrentFunction();
    return effects;
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return mode == Mode::kLoad ? RepVector<RegisterRepresentation::Simd128()>()
                               : RepVector<>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::WordPtr(),
                          RegisterRepresentation::WordPtr(),
                          RegisterRepresentation::Simd128()>();
  }

  static LaneKind LaneKindFromBytes(uint8_t size) {
    switch (size) {
      default:
        UNREACHABLE();
      case 1:
        return LaneKind::k8;
      case 2:
        return LaneKind::k16;
      case 4:
        return LaneKind::k32;
      case 8:
        return LaneKind::k64;
    }
  }

  Simd128LaneMemoryOp(OpIndex base, OpIndex index, OpIndex value, Mode mode,
                      Kind kind, LaneKind lane_kind, uint8_t lane, int offset)
      : Base(base, index, value),
        mode(mode),
        kind(kind),
        lane_kind(lane_kind),
        lane(lane),
        offset(offset) {}

  OpIndex base() const { return input(0); }
  OpIndex index() const { return input(1); }
  OpIndex value() const { return input(2); }
  uint8_t lane_size() const { return 1 << static_cast<uint8_t>(lane_kind); }

  void Validate(const Graph& graph) {
    DCHECK(!kind.tagged_base);
#if DEBUG
    uint8_t lane_count;
    switch (lane_kind) {
      case LaneKind::k8:
        lane_count = 16;
        break;
      case LaneKind::k16:
        lane_count = 8;
        break;
      case LaneKind::k32:
        lane_count = 4;
        break;
      case LaneKind::k64:
        lane_count = 2;
        break;
    }
    DCHECK_LT(lane, lane_count);
#endif
  }

  auto options() const {
    return std::tuple{mode, kind, lane_kind, lane, offset};
  }
  void PrintOptions(std::ostream& os) const;
};

#define FOREACH_SIMD_128_LOAD_TRANSFORM_OPCODE(V) \
  V(8x8S)                                         \
  V(8x8U)                                         \
  V(16x4S)                                        \
  V(16x4U)                                        \
  V(32x2S)                                        \
  V(32x2U)                                        \
  V(8Splat)                                       \
  V(16Splat)                                      \
  V(32Splat)                                      \
  V(64Splat)                                      \
  V(32Zero)                                       \
  V(64Zero)

// Load a value from `base() + index() + offset`, whose size is determinded by
// `transform_kind`, and generate a Simd128 value as follows:
// - From 8x8S to 32x2U (extend kinds), the loaded value has size 8. It is
//   interpreted as a vector of values according to the size of the kind, which
//   populate the even lanes of the generated value. The odd lanes are zero- or
//   sign-extended according to the kind.
// - For splat kinds, the loaded value's size is determined by the kind, all
//   lanes of the generated value are populated with the loaded value.
// - For "zero" kinds, the loaded value's size is determined by the kind, and
//   the generated value zero-extends the loaded value.
struct Simd128LoadTransformOp
    : FixedArityOperationT<2, Simd128LoadTransformOp> {
  using LoadKind = LoadOp::Kind;
  enum class TransformKind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_128_LOAD_TRANSFORM_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  LoadKind load_kind;
  TransformKind transform_kind;
  int offset;

  OpEffects Effects() const {
    OpEffects effects = OpEffects().CanReadMemory().CanDependOnChecks();
    if (load_kind.with_trap_handler) {
      effects = effects.CanLeaveCurrentFunction();
    }
    return effects;
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::WordPtr(),
                          RegisterRepresentation::WordPtr()>();
  }

  Simd128LoadTransformOp(V<WordPtr> base, V<WordPtr> index, LoadKind load_kind,
                         TransformKind transform_kind, int offset)
      : Base(base, index),
        load_kind(load_kind),
        transform_kind(transform_kind),
        offset(offset) {}

  V<WordPtr> base() const { return input<WordPtr>(0); }
  V<WordPtr> index() const { return input<WordPtr>(1); }

  void Validate(const Graph& graph) { DCHECK(!load_kind.tagged_base); }

  auto options() const { return std::tuple{load_kind, transform_kind, offset}; }
  void PrintOptions(std::ostream& os) const;
};

// Takes two Simd128 inputs and generates a Simd128 value. The 8-bit lanes of
// both inputs are numbered 0-31, and each output 8-bit lane is selected from
// among the input lanes according to `shuffle`.
struct Simd128ShuffleOp : FixedArityOperationT<2, Simd128ShuffleOp> {
  enum class Kind : uint8_t {
    kI8x1,
    kI8x2,
    kI8x4,
    kI8x8,
    kI8x16,
  };

  uint8_t shuffle[kSimd128Size] = {0};
  const Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128(),
                          RegisterRepresentation::Simd128()>();
  }

  Simd128ShuffleOp(V<Simd128> left, V<Simd128> right, Kind kind,
                   const uint8_t incoming_shuffle[kSimd128Size])
      : Base(left, right), kind(kind) {
    uint8_t count = 0;
    switch (kind) {
      default:
        UNREACHABLE();
      case Kind::kI8x1:
        count = 1;
        break;
      case Kind::kI8x2:
        count = 2;
        break;
      case Kind::kI8x4:
        count = 4;
        break;
      case Kind::kI8x8:
        count = 8;
        break;
      case Kind::kI8x16:
        count = 16;
        break;
    }
    std::copy_n(incoming_shuffle, count, shuffle);
  }

  V<Simd128> left() const { return input<Simd128>(0); }
  V<Simd128> right() const { return input<Simd128>(1); }

  void Validate(const Graph& graph) {
#if DEBUG
    constexpr uint8_t kNumberOfLanesForShuffle = 32;
    for (uint8_t index : shuffle) {
      DCHECK_LT(index, kNumberOfLanesForShuffle);
    }
#endif
  }

  auto options() const { return std::tuple{kind, shuffle}; }
  void PrintOptions(std::ostream& os) const;
};

// Load from memory and write the result into two registers, the first
// containing the even numbered elements and the other containing the odd
// elements.
struct Simd128LoadPairDeinterleaveOp
    : FixedArityOperationT<2, Simd128LoadPairDeinterleaveOp> {
  using LoadKind = LoadOp::Kind;
  enum class Kind : uint8_t {
    k8x32,
    k16x16,
    k32x8,
    k64x4,
  };

  LoadKind load_kind;
  Kind kind;

  OpEffects Effects() const {
    OpEffects effects = OpEffects().CanReadMemory().CanDependOnChecks();
    if (load_kind.with_trap_handler) {
      effects = effects.CanLeaveCurrentFunction();
    }
    return effects;
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128(),
                     RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::WordPtr(),
                          RegisterRepresentation::WordPtr()>();
  }

  Simd128LoadPairDeinterleaveOp(V<WordPtr> base, V<WordPtr> index,
                                LoadKind load_kind, Kind kind)
      : Base(base, index), load_kind(load_kind), kind(kind) {}

  V<WordPtr> base() const { return Base::input<WordPtr>(0); }
  V<WordPtr> index() const { return Base::input<WordPtr>(1); }

  uint32_t lane_size() const {
    switch (kind) {
      case Kind::k8x32:
        return 8;
      case Kind::k16x16:
        return 16;
      case Kind::k32x8:
        return 32;
      case Kind::k64x4:
        return 64;
    }
    UNREACHABLE();
  }

  void Validate(const Graph& graph) const { DCHECK(!load_kind.tagged_base); }

  auto options() const { return std::tuple{load_kind, kind}; }

  void PrintOptions(std::ostream& os) const;
};

#if V8_ENABLE_SIMD256

struct Simd256ConstantOp : FixedArityOperationT<0, Simd256ConstantOp> {
  static constexpr uint8_t kZero[kSimd256Size] = {};
  uint8_t value[kSimd256Size];

  static constexpr OpEffects effects = OpEffects();

  explicit Simd256ConstantOp(const uint8_t incoming_value[kSimd256Size])
      : Base() {
    std::copy(incoming_value, incoming_value + kSimd256Size, value);
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  void Validate(const Graph& graph) const {
    // TODO(14108): Validate.
  }

  bool IsZero() const { return std::memcmp(kZero, value, kSimd256Size) == 0; }

  auto options() const { return std::tuple{value}; }
  void PrintOptions(std::ostream& os) const;
};

struct Simd256Extract128LaneOp
    : FixedArityOperationT<1, Simd256Extract128LaneOp> {
  uint8_t lane;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd128()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd256()>();
  }

  Simd256Extract128LaneOp(V<Simd256> input, uint8_t lane)
      : Base(input), lane(lane) {}

  V<Simd256> input() const { return Base::input<Simd256>(0); }

  void Validate(const Graph& graph) const {
#if DEBUG
    DCHECK_LT(lane, 2);
#endif
  }

  auto options() const { return std::tuple{lane}; }
  void PrintOptions(std::ostream& os) const;
};

#define FOREACH_SIMD_256_LOAD_TRANSFORM_OPCODE(V) \
  V(8x16S)                                        \
  V(8x16U)                                        \
  V(8x8U)                                         \
  V(16x8S)                                        \
  V(16x8U)                                        \
  V(32x4S)                                        \
  V(32x4U)                                        \
  V(8Splat)                                       \
  V(16Splat)                                      \
  V(32Splat)                                      \
  V(64Splat)

struct Simd256LoadTransformOp
    : FixedArityOperationT<2, Simd256LoadTransformOp> {
  using LoadKind = LoadOp::Kind;
  enum class TransformKind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_LOAD_TRANSFORM_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  LoadKind load_kind;
  TransformKind transform_kind;
  int offset;

  OpEffects Effects() const {
    OpEffects effects = OpEffects().CanReadMemory().CanDependOnChecks();
    if (load_kind.with_trap_handler) {
      effects = effects.CanLeaveCurrentFunction();
    }
    return effects;
  }

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::WordPtr(),
                          RegisterRepresentation::WordPtr()>();
  }

  Simd256LoadTransformOp(V<WordPtr> base, V<WordPtr> index, LoadKind load_kind,
                         TransformKind transform_kind, int offset)
      : Base(base, index),
        load_kind(load_kind),
        transform_kind(transform_kind),
        offset(offset) {}

  V<WordPtr> base() const { return input<WordPtr>(0); }
  V<WordPtr> index() const { return input<WordPtr>(1); }

  void Validate(const Graph& graph) { DCHECK(!load_kind.tagged_base); }

  auto options() const { return std::tuple{load_kind, transform_kind, offset}; }
  void PrintOptions(std::ostream& os) const;
};

#define FOREACH_SIMD_256_UNARY_EXTENSION_OPCODE(V) \
  V(I16x16SConvertI8x16)                           \
  V(I16x16UConvertI8x16)                           \
  V(I32x8SConvertI16x8)                            \
  V(I32x8UConvertI16x8)                            \
  V(I64x4SConvertI32x4)                            \
  V(I64x4UConvertI32x4)

#define FOREACH_SIMD_256_UNARY_OPTIONAL_OPCODE(V) \
  V(F32x8Ceil)                                    \
  V(F32x8Floor)                                   \
  V(F32x8Trunc)                                   \
  V(F32x8NearestInt)

#define FOREACH_SIMD_256_UNARY_OPCODE(V)    \
  V(S256Not)                                \
  V(I8x32Abs)                               \
  V(I8x32Neg)                               \
  V(I16x16ExtAddPairwiseI8x32S)             \
  V(I16x16ExtAddPairwiseI8x32U)             \
  V(I32x8ExtAddPairwiseI16x16S)             \
  V(I32x8ExtAddPairwiseI16x16U)             \
  V(I16x16Abs)                              \
  V(I16x16Neg)                              \
  V(I32x8Abs)                               \
  V(I32x8Neg)                               \
  V(F32x8Abs)                               \
  V(F32x8Neg)                               \
  V(F32x8Sqrt)                              \
  V(F64x4Abs)                               \
  V(F64x4Neg)                               \
  V(F64x4Sqrt)                              \
  V(I32x8UConvertF32x8)                     \
  V(I32x8SConvertF32x8)                     \
  V(F32x8UConvertI32x8)                     \
  V(F32x8SConvertI32x8)                     \
  V(I32x8RelaxedTruncF32x8S)                \
  V(I32x8RelaxedTruncF32x8U)                \
  FOREACH_SIMD_256_UNARY_OPTIONAL_OPCODE(V) \
  FOREACH_SIMD_256_UNARY_EXTENSION_OPCODE(V)

struct Simd256UnaryOp : FixedArityOperationT<1, Simd256UnaryOp> {
  // clang-format off
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_UNARY_OPCODE(DEFINE_KIND)
    kFirstExtensionOp = kI16x16SConvertI8x16,
    kLastExtensionOp = kI64x4UConvertI32x4,
#undef DEFINE_KIND
  };
  // clang-format on

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    if (kind >= Kind::kFirstExtensionOp) {
      return MaybeRepVector<RegisterRepresentation::Simd128()>();
    } else {
      return MaybeRepVector<RegisterRepresentation::Simd256()>();
    }
  }

  Simd256UnaryOp(OpIndex input, Kind kind) : Base(input), kind(kind) {}

  OpIndex input() const { return Base::input(0); }


  auto options() const { return std::tuple{kind}; }
};
std::ostream& operator<<(std::ostream& os, Simd256UnaryOp::Kind kind);

#define FOREACH_SIMD_256_BINARY_EXTENSION_OPCODE(V) \
  V(I64x4ExtMulI32x4S)                              \
  V(I64x4ExtMulI32x4U)                              \
  V(I32x8ExtMulI16x8S)                              \
  V(I32x8ExtMulI16x8U)                              \
  V(I16x16ExtMulI8x16S)                             \
  V(I16x16ExtMulI8x16U)

#define FOREACH_SIMD_256_BINARY_OPCODE(V) \
  V(I8x32Eq)                              \
  V(I8x32Ne)                              \
  V(I8x32GtS)                             \
  V(I8x32GtU)                             \
  V(I8x32GeS)                             \
  V(I8x32GeU)                             \
  V(I16x16Eq)                             \
  V(I16x16Ne)                             \
  V(I16x16GtS)                            \
  V(I16x16GtU)                            \
  V(I16x16GeS)                            \
  V(I16x16GeU)                            \
  V(I32x8Eq)                              \
  V(I32x8Ne)                              \
  V(I32x8GtS)                             \
  V(I32x8GtU)                             \
  V(I32x8GeS)                             \
  V(I32x8GeU)                             \
  V(F32x8Eq)                              \
  V(F32x8Ne)                              \
  V(F32x8Lt)                              \
  V(F32x8Le)                              \
  V(F64x4Eq)                              \
  V(F64x4Ne)                              \
  V(F64x4Lt)                              \
  V(F64x4Le)                              \
  V(S256And)                              \
  V(S256AndNot)                           \
  V(S256Or)                               \
  V(S256Xor)                              \
  V(I8x32SConvertI16x16)                  \
  V(I8x32UConvertI16x16)                  \
  V(I8x32Add)                             \
  V(I8x32AddSatS)                         \
  V(I8x32AddSatU)                         \
  V(I8x32Sub)                             \
  V(I8x32SubSatS)                         \
  V(I8x32SubSatU)                         \
  V(I8x32MinS)                            \
  V(I8x32MinU)                            \
  V(I8x32MaxS)                            \
  V(I8x32MaxU)                            \
  V(I8x32RoundingAverageU)                \
  V(I16x16SConvertI32x8)                  \
  V(I16x16UConvertI32x8)                  \
  V(I16x16Add)                            \
  V(I16x16AddSatS)                        \
  V(I16x16AddSatU)                        \
  V(I16x16Sub)                            \
  V(I16x16SubSatS)                        \
  V(I16x16SubSatU)                        \
  V(I16x16Mul)                            \
  V(I16x16MinS)                           \
  V(I16x16MinU)                           \
  V(I16x16MaxS)                           \
  V(I16x16MaxU)                           \
  V(I16x16RoundingAverageU)               \
  V(I32x8Add)                             \
  V(I32x8Sub)                             \
  V(I32x8Mul)                             \
  V(I32x8MinS)                            \
  V(I32x8MinU)                            \
  V(I32x8MaxS)                            \
  V(I32x8MaxU)                            \
  V(I32x8DotI16x16S)                      \
  V(I64x4Add)                             \
  V(I64x4Sub)                             \
  V(I64x4Mul)                             \
  V(I64x4Eq)                              \
  V(I64x4Ne)                              \
  V(I64x4GtS)                             \
  V(I64x4GeS)                             \
  V(F32x8Add)                             \
  V(F32x8Sub)                             \
  V(F32x8Mul)                             \
  V(F32x8Div)                             \
  V(F32x8Min)                             \
  V(F32x8Max)                             \
  V(F32x8Pmin)                            \
  V(F32x8Pmax)                            \
  V(F64x4Add)                             \
  V(F64x4Sub)                             \
  V(F64x4Mul)                             \
  V(F64x4Div)                             \
  V(F64x4Min)                             \
  V(F64x4Max)                             \
  V(F64x4Pmin)                            \
  V(F64x4Pmax)                            \
  V(F32x8RelaxedMin)                      \
  V(F32x8RelaxedMax)                      \
  V(F64x4RelaxedMin)                      \
  V(F64x4RelaxedMax)                      \
  V(I16x16DotI8x32I7x32S)                 \
  FOREACH_SIMD_256_BINARY_EXTENSION_OPCODE(V)

struct Simd256BinopOp : FixedArityOperationT<2, Simd256BinopOp> {
  // clang-format off
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_BINARY_OPCODE(DEFINE_KIND)
    kFirstExtensionOp = kI64x4ExtMulI32x4S,
    kLastExtensionOp = kI16x16ExtMulI8x16U,
#undef DEFINE_KIND
  };
  // clang-format on

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    if (kind >= Kind::kFirstExtensionOp) {
      return MaybeRepVector<RegisterRepresentation::Simd128(),
                            RegisterRepresentation::Simd128()>();
    } else {
      return MaybeRepVector<RegisterRepresentation::Simd256(),
                            RegisterRepresentation::Simd256()>();
    }
  }

  Simd256BinopOp(OpIndex left, OpIndex right, Kind kind)
      : Base(left, right), kind(kind) {}

  OpIndex left() const { return input(0); }
  OpIndex right() const { return input(1); }


  auto options() const { return std::tuple{kind}; }
};

std::ostream& operator<<(std::ostream& os, Simd256BinopOp::Kind kind);

#define FOREACH_SIMD_256_SHIFT_OPCODE(V) \
  V(I16x16Shl)                           \
  V(I16x16ShrS)                          \
  V(I16x16ShrU)                          \
  V(I32x8Shl)                            \
  V(I32x8ShrS)                           \
  V(I32x8ShrU)                           \
  V(I64x4Shl)                            \
  V(I64x4ShrU)

struct Simd256ShiftOp : FixedArityOperationT<2, Simd256ShiftOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_SHIFT_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd256(),
                          RegisterRepresentation::Word32()>();
  }

  Simd256ShiftOp(V<Simd256> input, V<Word32> shift, Kind kind)
      : Base(input, shift), kind(kind) {}

  V<Simd256> input() const { return Base::input<Simd256>(0); }
  V<Word32> shift() const { return Base::input<Word32>(1); }


  auto options() const { return std::tuple{kind}; }
};
V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream& os,
                                           Simd256ShiftOp::Kind kind);

#define FOREACH_SIMD_256_TERNARY_MASK_OPCODE(V) \
  V(S256Select)                                 \
  V(I8x32RelaxedLaneSelect)                     \
  V(I16x16RelaxedLaneSelect)                    \
  V(I32x8RelaxedLaneSelect)                     \
  V(I64x4RelaxedLaneSelect)

#define FOREACH_SIMD_256_TERNARY_OTHER_OPCODE(V) \
  V(F32x8Qfma)                                   \
  V(F32x8Qfms)                                   \
  V(F64x4Qfma)                                   \
  V(F64x4Qfms)                                   \
  V(I32x8DotI8x32I7x32AddS)

#define FOREACH_SIMD_256_TERNARY_OPCODE(V) \
  FOREACH_SIMD_256_TERNARY_MASK_OPCODE(V)  \
  FOREACH_SIMD_256_TERNARY_OTHER_OPCODE(V)

struct Simd256TernaryOp : FixedArityOperationT<3, Simd256TernaryOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_TERNARY_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd256(),
                          RegisterRepresentation::Simd256(),
                          RegisterRepresentation::Simd256()>();
  }

  Simd256TernaryOp(V<Simd256> first, V<Simd256> second, V<Simd256> third,
                   Kind kind)
      : Base(first, second, third), kind(kind) {}

  V<Simd256> first() const { return input<Simd256>(0); }
  V<Simd256> second() const { return input<Simd256>(1); }
  V<Simd256> third() const { return input<Simd256>(2); }


  auto options() const { return std::tuple{kind}; }
};
std::ostream& operator<<(std::ostream& os, Simd256TernaryOp::Kind kind);

#define FOREACH_SIMD_256_SPLAT_OPCODE(V) \
  V(I8x32)                               \
  V(I16x16)                              \
  V(I32x8)                               \
  V(I64x4)                               \
  V(F16x16)                              \
  V(F32x8)                               \
  V(F64x4)

struct Simd256SplatOp : FixedArityOperationT<1, Simd256SplatOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_SPLAT_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };

  Kind kind;

  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    switch (kind) {
      case Kind::kI8x32:
      case Kind::kI16x16:
      case Kind::kI32x8:
        return MaybeRepVector<RegisterRepresentation::Word32()>();
      case Kind::kI64x4:
        return MaybeRepVector<RegisterRepresentation::Word64()>();
      case Kind::kF16x16:
      case Kind::kF32x8:
        return MaybeRepVector<RegisterRepresentation::Float32()>();
      case Kind::kF64x4:
        return MaybeRepVector<RegisterRepresentation::Float64()>();
    }
    UNREACHABLE();
  }

  Simd256SplatOp(OpIndex input, Kind kind) : Base(input), kind(kind) {}

  OpIndex input() const { return Base::input(0); }


  auto options() const { return std::tuple{kind}; }
};
std::ostream& operator<<(std::ostream& os, Simd256SplatOp::Kind kind);

struct SimdPack128To256Op : FixedArityOperationT<2, SimdPack128To256Op> {
  static constexpr OpEffects effects = OpEffects();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd128(),
                          RegisterRepresentation::Simd128()>();
  }

  SimdPack128To256Op(V<Simd128> left, V<Simd128> right) : Base(left, right) {}
  V<Simd128> left() const { return Base::input<Simd128>(0); }
  V<Simd128> right() const { return Base::input<Simd128>(1); }
  auto options() const { return std::tuple{}; }
};

#ifdef V8_TARGET_ARCH_X64
struct Simd256ShufdOp : FixedArityOperationT<1, Simd256ShufdOp> {
  static constexpr OpEffects effects = OpEffects();
  uint8_t control;

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd256()>();
  }

  Simd256ShufdOp(V<Simd256> input, uint8_t control)
      : Base(input), control(control) {}

  V<Simd256> input() const { return Base::input<Simd256>(0); }


  auto options() const { return std::tuple{control}; }

  void PrintOptions(std::ostream& os) const;
};

struct Simd256ShufpsOp : FixedArityOperationT<2, Simd256ShufpsOp> {
  static constexpr OpEffects effects = OpEffects();
  uint8_t control;

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd256(),
                          RegisterRepresentation::Simd256()>();
  }

  Simd256ShufpsOp(V<Simd256> left, V<Simd256> right, uint8_t control)
      : Base(left, right), control(control) {}

  V<Simd256> left() const { return Base::input<Simd256>(0); }
  V<Simd256> right() const { return Base::input<Simd256>(1); }


  auto options() const { return std::tuple{control}; }

  void PrintOptions(std::ostream& os) const;
};

#define FOREACH_SIMD_256_UNPACK_OPCODE(V) \
  V(32x8Low)                              \
  V(32x8High)
struct Simd256UnpackOp : FixedArityOperationT<2, Simd256UnpackOp> {
  enum class Kind : uint8_t {
#define DEFINE_KIND(kind) k##kind,
    FOREACH_SIMD_256_UNPACK_OPCODE(DEFINE_KIND)
#undef DEFINE_KIND
  };
  static constexpr OpEffects effects = OpEffects();
  Kind kind;

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Simd256()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<RegisterRepresentation::Simd256(),
                          RegisterRepresentation::Simd256()>();
  }

  Simd256UnpackOp(V<Simd256> left, V<Simd256> right, Kind kind)
      : Base(left, right), kind(kind) {}

  V<Simd256> left() const { return Base::input<Simd256>(0); }
  V<Simd256> right() const { return Base::input<Simd256>(1); }


  auto options() const { return std::tuple{kind}; }
};
std::ostream& operator<<(std::ostream& os, Simd256UnpackOp::Kind kind);
#endif  // V8_TARGET_ARCH_X64

#endif  // V8_ENABLE_SIMD256
#endif  // V8_ENABLE_SIMD128

#if V8_ENABLE_WEBASSEMBLY

struct LoadStackPointerOp : FixedArityOperationT<0, LoadStackPointerOp> {
  // TODO(nicohartmann@): Review effects.
  static constexpr OpEffects effects = OpEffects().CanReadMemory();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::WordPtr()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  auto options() const { return std::tuple{}; }
};

struct SetStackPointerOp : FixedArityOperationT<1, SetStackPointerOp> {
  // TODO(nicohartmann@): Review effects.
  static constexpr OpEffects effects = OpEffects().CanCallAnything();

  OpIndex value() const { return Base::input(0); }

  explicit SetStackPointerOp(OpIndex value) : Base(value) {}

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::WordPtr()>();
  }

  auto options() const { return std::tuple{}; }
};

#endif  // V8_ENABLE_WEBASSEMBLY

#ifdef V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA
struct GetContinuationPreservedEmbedderDataOp
    : FixedArityOperationT<0, GetContinuationPreservedEmbedderDataOp> {
  static constexpr OpEffects effects = OpEffects().CanReadOffHeapMemory();

  base::Vector<const RegisterRepresentation> outputs_rep() const {
    return RepVector<RegisterRepresentation::Tagged()>();
  }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return {};
  }

  GetContinuationPreservedEmbedderDataOp() : Base() {}


  auto options() const { return std::tuple{}; }
};

struct SetContinuationPreservedEmbedderDataOp
    : FixedArityOperationT<1, SetContinuationPreservedEmbedderDataOp> {
  static constexpr OpEffects effects = OpEffects().CanWriteOffHeapMemory();

  base::Vector<const RegisterRepresentation> outputs_rep() const { return {}; }

  base::Vector<const MaybeRegisterRepresentation> inputs_rep(
      ZoneVector<MaybeRegisterRepresentation>& storage) const {
    return MaybeRepVector<MaybeRegisterRepresentation::Tagged()>();
  }

  explicit SetContinuationPreservedEmbedderDataOp(V<Object> value)
      : Base(value) {}


  auto options() const { return std::tuple{}; }
};
#endif  // V8_ENABLE_CONTINUATION_PRESERVED_EMBEDDER_DATA

#define OPERATION_EFFECTS_CASE(Name) Name##Op::EffectsIfStatic(),
static constexpr std::optional<OpEffects>
    kOperationEffectsTable[kNumberOfOpcodes] = {
        TURBOSHAFT_OPERATION_LIST(OPERATION_EFFECTS_CASE)};
#undef OPERATION_EFFECTS_CASE

template <class Op>
const Opcode OperationT<Op>::opcode = operation_to_opcode<Op>::value;

template <Opcode opcode>
struct opcode_to_operation_map {};

#define OPERATION_OPCODE_MAP_CASE(Name)             \
  template <>                                       \
  struct opcode_to_operation_map<Opcode::k##Name> { \
    using Op = Name##Op;                            \
  };
TURBOSHAFT_OPERATION_LIST(OPERATION_OPCODE_MAP_CASE)
#undef OPERATION_OPCODE_MAP_CASE

template <class Op, class = void>
struct static_operation_input_count : std::integral_constant<uint32_t, 0> {};
template <class Op>
struct static_operation_input_count<Op, std::void_t<decltype(Op::inputs)>>
    : std::integral_constant<uint32_t, sizeof(Op::inputs) / sizeof(OpIndex)> {};
constexpr size_t kOperationSizeTable[kNumberOfOpcodes] = {
#define OPERATION_SIZE(Name) sizeof(Name##Op),
    TURBOSHAFT_OPERATION_LIST(OPERATION_SIZE)
#undef OPERATION_SIZE
};
constexpr size_t kOperationSizeDividedBySizeofOpIndexTable[kNumberOfOpcodes] = {
#define OPERATION_SIZE(Name) (sizeof(Name##Op) / sizeof(OpIndex)),
    TURBOSHAFT_OPERATION_LIST(OPERATION_SIZE)
#undef OPERATION_SIZE
};

inline base::Vector<const OpIndex> Operation::inputs() const {
  // This is actually undefined behavior, since we use the `this` pointer to
  // access an adjacent object.
  const OpIndex* ptr = reinterpret_cast<const OpIndex*>(
      reinterpret_cast<const char*>(this) +
      kOperationSizeTable[OpcodeIndex(opcode)]);
  return {ptr, input_count};
}

inline OpEffects Operation::Effects() const {
  if (auto prop = kOperationEffectsTable[OpcodeIndex(opcode)]) {
    return *prop;
  }
  switch (opcode) {
    case Opcode::kLoad:
      return Cast<LoadOp>().Effects();
    case Opcode::kStore:
      return Cast<StoreOp>().Effects();
    case Opcode::kCall:
      return Cast<CallOp>().Effects();
    case Opcode::kDidntThrow:
      return Cast<DidntThrowOp>().Effects();
    case Opcode::kTaggedBitcast:
      return Cast<TaggedBitcastOp>().Effects();
    case Opcode::kAtomicRMW:
      return Cast<AtomicRMWOp>().Effects();
    case Opcode::kAtomicWord32Pair:
      return Cast<AtomicWord32PairOp>().Effects();
    case Opcode::kJSStackCheck:
      return Cast<JSStackCheckOp>().Effects();
#if V8_ENABLE_WEBASSEMBLY
    case Opcode::kWasmStackCheck:
      return Cast<WasmStackCheckOp>().Effects();
    case Opcode::kStructGet:
      return Cast<StructGetOp>().Effects();
    case Opcode::kStructSet:
      return Cast<StructSetOp>().Effects();
    case Opcode::kArrayGet:
      return Cast<ArrayGetOp>().Effects();
    case Opcode::kArraySet:
      return Cast<ArraySetOp>().Effects();
    case Opcode::kStructAtomicRMW:
      return Cast<StructAtomicRMWOp>().Effects();
    case Opcode::kArrayAtomicRMW:
      return Cast<ArrayAtomicRMWOp>().Effects();
    case Opcode::kArrayLength:
      return Cast<ArrayLengthOp>().Effects();
#endif  // V8_ENABLE_WEBASSEMBLY
#if V8_ENABLE_SIMD128
    case Opcode::kSimd128LaneMemory:
      return Cast<Simd128LaneMemoryOp>().Effects();
    case Opcode::kSimd128LoadTransform:
      return Cast<Simd128LoadTransformOp>().Effects();
    case Opcode::kSimd128LoadPairDeinterleave:
      return Cast<Simd128LoadPairDeinterleaveOp>().Effects();
#if V8_ENABLE_SIMD256
    case Opcode::kSimd256LoadTransform:
      return Cast<Simd256LoadTransformOp>().Effects();
#endif  // V8_ENABLE_SIMD256
#endif  // V8_ENABLE_SIMD128
#if V8_ENABLE_SANDBOX
    case Opcode::kLoadTrustedPointer:
      return Cast<LoadTrustedPointerOp>().Effects();
#endif  // V8_ENABLE_SANDBOX
    default:
      UNREACHABLE();
  }
}

// static
inline size_t Operation::StorageSlotCount(Opcode opcode, size_t input_count) {
  size_t size = kOperationSizeDividedBySizeofOpIndexTable[OpcodeIndex(opcode)];
  constexpr size_t r = sizeof(OperationStorageSlot) / sizeof(OpIndex);
  static_assert(sizeof(OperationStorageSlot) % sizeof(OpIndex) == 0);
  return std::max<size_t>(2, (r - 1 + size + input_count) / r);
}

V8_INLINE bool CanBeUsedAsInput(const Operation& op) {
  return op.Is<FrameStateOp>() || op.outputs_rep().size() > 0;
}

inline base::Vector<const RegisterRepresentation> Operation::outputs_rep()
    const {
  switch (opcode) {
#define CASE(type)                         \
  case Opcode::k##type: {                  \
    const type##Op& op = Cast<type##Op>(); \
    return op.outputs_rep();               \
  }
    TURBOSHAFT_OPERATION_LIST(CASE)
#undef CASE
  }
  UNREACHABLE();
}

inline base::Vector<const MaybeRegisterRepresentation> Operation::inputs_rep(
    ZoneVector<MaybeRegisterRepresentation>& storage) const {
  switch (opcode) {
#define CASE(type)                         \
  case Opcode::k##type: {                  \
    const type##Op& op = Cast<type##Op>(); \
    return op.inputs_rep(storage);         \
  }
    TURBOSHAFT_OPERATION_LIST(CASE)
#undef CASE
  }
  UNREACHABLE();
}

bool IsUnlikelySuccessor(const Block* block, const Block* successor,
                         const Graph& graph);

// Analyzers should skip (= ignore) operations for which ShouldSkipOperation
// returns true. This happens for:
//  - DeadOp: this means that a previous Analyzer decided that this operation is
//    dead.
//  - Operations that are not RequiredWhenUnused, and whose saturated_use_count
//    is 0: this corresponds to pure operations that have no uses.
V8_EXPORT_PRIVATE V8_INLINE bool ShouldSkipOperation(const Operation& op) {
  if (op.Is<DeadOp>()) return true;
  return op.saturated_use_count.Is(0) && !op.IsRequiredWhenUnused();
}

namespace detail {
// Defining `input_count` to compute the number of OpIndex inputs of an
// operation.

// There is one overload for each possible type of parameters for all
// Operations rather than a default generic overload, so that we don't
// accidentally forget some types (eg, if a new Operation takes its inputs as a
// std::vector<OpIndex>, we shouldn't count this as "0 inputs because it's
// neither raw OpIndex nor base::Vector<OpIndex>", which a generic overload
// might do).

// Base case
constexpr size_t input_count() { return 0; }

// All parameters that are not OpIndex and should thus not count towards the
// "input_count" of the operations.
template <typename T>
constexpr size_t input_count(T)
  requires(std::is_enum_v<T> || std::is_integral_v<T> ||
           std::is_floating_point_v<T> || base::is_strong_alias_v<T>)
{
  return 0;
}
// TODO(42203211): The first parameter should be just DirectHandle<T> and
// MaybeDirectHandle<T> but now it does not compile with implicit Handle to
// DirectHandle conversions.
template <template <typename> typename HandleType, typename T>
constexpr size_t input_count(const HandleType<T>)
  requires(std::disjunction_v<
           std::is_convertible<HandleType<T>, DirectHandle<T>>,
           std::is_convertible<HandleType<T>, MaybeDirectHandle<T>>>)
{
  return 0;
}
template <typename T>
constexpr size_t input_count(const base::Flags<T>) {
  return 0;
}
constexpr size_t input_count(const Block*) { return 0; }
constexpr size_t input_count(const TSCallDescriptor*) { return 0; }
constexpr size_t input_count(base::Vector<EffectHandler>) { return 0; }
constexpr size_t input_count(const char*) { return 0; }
constexpr size_t input_count(const DeoptimizeParameters*) { return 0; }
constexpr size_t input_count(const FastApiCallParameters*) { return 0; }
constexpr size_t input_count(const FrameStateData*) { return 0; }
constexpr size_t input_count(const base::Vector<SwitchOp::Case>) { return 0; }
constexpr size_t input_count(LoadOp::Kind) { return 0; }
constexpr size_t input_count(RegisterRepresentation) { return 0; }
constexpr size_t input_count(MemoryRepresentation) { return 0; }
constexpr size_t input_count(OpEffects) { return 0; }
constexpr size_t input_count(ExternalPointerTagRange) { return 0; }
#if V8_ENABLE_SANDBOX
constexpr size_t input_count(IndirectPointerTagRange) { return 0; }
#endif
inline size_t input_count(const ElementsTransition) { return 0; }
inline size_t input_count(const ElementsTransitionWithMultipleSources) {
  return 0;
}
inline size_t input_count(const FeedbackSource) { return 0; }
inline size_t input_count(const ZoneRefSet<Map>) { return 0; }
inline size_t input_count(const NameRef) { return 0; }
inline size_t input_count(const WeakHomomorphicFixedArrayRef) { return 0; }
inline size_t input_count(ConstantOp::Storage) { return 0; }
inline size_t input_count(Type) { return 0; }
inline size_t input_count(base::Vector<const RegisterRepresentation>) {
  return 0;
}
constexpr size_t input_count(std::optional<AtomicMemoryOrder>) { return 0; }
#ifdef V8_ENABLE_WEBASSEMBLY
constexpr size_t input_count(const wasm::WasmGlobal*) { return 0; }
constexpr size_t input_count(const wasm::StructType*) { return 0; }
constexpr size_t input_count(const wasm::ArrayType*) { return 0; }
constexpr size_t input_count(wasm::ValueType) { return 0; }
constexpr size_t input_count(WasmTypeCheckConfig) { return 0; }
constexpr size_t input_count(wasm::ModuleTypeIndex) { return 0; }
#endif

// All parameters that are OpIndex-like (ie, OpIndex, and OpIndex containers)
constexpr size_t input_count(OpIndex) { return 1; }
constexpr size_t input_count(OptionalOpIndex) { return 1; }
constexpr size_t input_count(base::Vector<const OpIndex> inputs) {
  return inputs.size();
}
template <typename T>
constexpr size_t input_count(base::Vector<const V<T>> inputs) {
  return inputs.size();
}
}  // namespace detail

template <typename Op, typename... Args>
Op* CreateOperation(base::SmallVector<OperationStorageSlot, 32>& storage,
                    Args... args) {
  size_t input_count = (0 + ... + detail::input_count(args));
  size_t size = Operation::StorageSlotCount(Op::opcode, input_count);
  storage.resize(size);
  Op* op = new (storage.data()) Op(args...);
  // Checking that the {input_count} we computed is at least the actual
  // input_count of the operation. {input_count} could be greater in the case of
  // OptionalOpIndex: they count for 1 input when computing {input_count} here,
  // but in Operations, they only count for 1 input when they are valid.
  DCHECK_GE(input_count, op->input_count);
  return op;
}

template <typename F>
auto VisitOperation(const Operation& op, F&& f) {
  switch (op.opcode) {
#define CASE(name)      \
  case Opcode::k##name: \
    return f(op.Cast<name##Op>());
    TURBOSHAFT_OPERATION_LIST(CASE)
#undef CASE
  }
  UNREACHABLE();
}

// Checking that throwing operations have the required members and options.
namespace details {

template <typename T, typename Tuple>
struct TupleHasType;

template <typename T, typename... Ts>
struct TupleHasType<T, std::tuple<Ts...>> {
  static constexpr bool value = (std::is_same_v<T, Ts> || ...);
};

template <typename Op, typename = void>
struct ThrowingOpHasProperMembers : std::false_type {};
template <typename Op>
struct ThrowingOpHasProperMembers<
    Op, std::void_t<std::conjunction<decltype(Op::kOutputRepsStorage),
                                     decltype(Op::lazy_deopt_on_throw)>>>
    : std::true_type {};

template <typename Op, typename = void>
struct ThrowingOpHasLazyDeoptOption : std::false_type {};

template <typename Op>
struct ThrowingOpHasLazyDeoptOption<
    Op, std::enable_if_t<TupleHasType<
            LazyDeoptOnThrow, decltype(std::declval<Op>().options())>::value>>
    : std::true_type {};

// CallOp has special handling because its outputs_rep are dynamic (and found on
// its call descriptor).
template <>
struct ThrowingOpHasLazyDeoptOption<CallOp, void> : std::true_type {};
template <>
struct ThrowingOpHasProperMembers<CallOp, void> : std::true_type {};

template <>
struct ThrowingOpHasProperMembers<FastApiCallOp, void> : std::true_type {};
}  // namespace details

#define THROWING_OP_LOOKS_VALID(Name)                             \
  static_assert(details::ThrowingOpHasProperMembers<Name##Op>()); \
  static_assert(details::ThrowingOpHasLazyDeoptOption<Name##Op>());
TURBOSHAFT_THROWING_OPERATIONS_LIST(THROWING_OP_LOOKS_VALID)
#undef THROWING_OP_LOOKS_VALID

// DecideObjectIsSmi tries to figure out if an OpIndex can be a Smi or not. For
// instance, a SmiConstant is always a Smi, and a HeapObject constant is never a
// Smi.
enum class IsSmiDecision : uint8_t { kUnknown, kTrue, kFalse };
IsSmiDecision DecideObjectIsSmi(const Graph& graph, V<Object> idx,
                                int depth = 0);

}  // namespace v8::internal::compiler::turboshaft

#endif  // V8_COMPILER_TURBOSHAFT_OPERATIONS_H_
