/*
 * Copyright 2015 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "wasm/WasmIonCompile.h"

#include "mozilla/DebugOnly.h"

#include <algorithm>

#include "jit/ABIArgGenerator.h"
#include "jit/CodeGenerator.h"
#include "jit/CompileInfo.h"
#include "jit/Ion.h"
#include "jit/IonOptimizationLevels.h"
#include "jit/MIR-wasm.h"
#include "jit/MIR.h"
#include "jit/ShuffleAnalysis.h"
#include "js/GCAPI.h"       // JS::AutoSuppressGCAnalysis
#include "js/ScalarType.h"  // js::Scalar::Type
#include "util/DifferentialTesting.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmBuiltinModule.h"
#include "wasm/WasmBuiltins.h"
#include "wasm/WasmCodegenTypes.h"
#include "wasm/WasmGC.h"
#include "wasm/WasmGcObject.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmOpIter.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmStacks.h"
#include "wasm/WasmStubs.h"
#include "wasm/WasmValidate.h"

using namespace js;
using namespace js::jit;
using namespace js::wasm;

using mozilla::Nothing;

namespace {

using UniqueCompileInfo = UniquePtr<CompileInfo>;
using UniqueCompileInfoVector = Vector<UniqueCompileInfo, 1, SystemAllocPolicy>;

using BlockVector = Vector<MBasicBlock*, 8, SystemAllocPolicy>;
using DefVector = Vector<MDefinition*, 8, SystemAllocPolicy>;
using ControlInstructionVector =
    Vector<MControlInstruction*, 8, SystemAllocPolicy>;

// [SMDOC] WebAssembly Exception Handling in Ion
// =======================================================
//
// ## Throwing instructions
//
// Wasm exceptions can be thrown by either a throw instruction (local throw),
// or by a wasm call.
//
// ## The "catching try control"
//
// We know we are in try-code if there is a surrounding ControlItem with
// LabelKind::Try. The innermost such control is called the
// "catching try control".
//
// ## Throws without a catching try control
//
// Such throws are implemented with an instance call that triggers the exception
// unwinding runtime. The exception unwinding runtime will not return to the
// function.
//
// ## "landing pad" and "pre-pad" blocks
//
// When an exception is thrown, the unwinder will search for the nearest
// enclosing try block and redirect control flow to it. The code that executes
// before any catch blocks is called the 'landing pad'. The 'landing pad' is
// responsible to:
//   1. Consume the pending exception state from
//      Instance::pendingException(Tag)
//   2. Branch to the correct catch block, or else rethrow
//
// There is one landing pad for each try block. The immediate predecessors of
// the landing pad are called 'pre-pad' blocks. There is one pre-pad block per
// throwing instruction.
//
// ## Creating pre-pad blocks
//
// There are two possible sorts of pre-pad blocks, depending on whether we
// are branching after a local throw instruction, or after a wasm call:
//
// - If we encounter a local throw, we create the exception and tag objects,
//   store them to Instance::pendingException(Tag), and then jump to the
//   landing pad.
//
// - If we encounter a wasm call, we construct a MWasmCallCatchable which is a
//   control instruction with either a branch to a fallthrough block or
//   to a pre-pad block.
//
//   The pre-pad block for a wasm call is empty except for a jump to the
//   landing pad. It only exists to avoid critical edges which when split would
//   violate the invariants of MWasmCallCatchable. The pending exception state
//   is taken care of by the unwinder.
//
// Each pre-pad ends with a pending jump to the landing pad. The pending jumps
// to the landing pad are tracked in `tryPadPatches`. These are called
// "pad patches".
//
// ## Creating the landing pad
//
// When we exit try-code, we check if tryPadPatches has captured any control
// instructions (pad patches). If not, we don't compile any catches and we mark
// the rest as dead code.
//
// If there are pre-pad blocks, we join them to create a landing pad (or just
// "pad"). The pad's last two slots are the caught exception, and the
// exception's tag object.
//
// There are three different forms of try-catch/catch_all Wasm instructions,
// which result in different form of landing pad.
//
// 1. A catchless try, so a Wasm instruction of the form "try ... end".
//    - In this case, we end the pad by rethrowing the caught exception.
//
// 2. A single catch_all after a try.
//    - If the first catch after a try is a catch_all, then there won't be
//      any more catches, but we need the exception and its tag object, in
//      case the code in a catch_all contains "rethrow" instructions.
//      - The Wasm instruction "rethrow", gets the exception and tag object to
//        rethrow from the last two slots of the landing pad which, due to
//        validation, is the l'th surrounding ControlItem.
//      - We immediately GoTo to a new block after the pad and pop both the
//        exception and tag object, as we don't need them anymore in this case.
//
// 3. Otherwise, there is one or more catch code blocks following.
//    - In this case, we construct the landing pad by creating a sequence
//      of compare and branch blocks that compare the pending exception tag
//      object to the tag object of the current tagged catch block. This is
//      done incrementally as we visit each tagged catch block in the bytecode
//      stream. At every step, we update the ControlItem's block to point to
//      the next block to be created in the landing pad sequence. The final
//      block will either be a rethrow, if there is no catch_all, or else a
//      jump to a catch_all block.

struct TryControl {
  // Branches to bind to the try's landing pad.
  ControlInstructionVector landingPadPatches;
  // For `try_table`, the list of tagged catches and labels to branch to.
  TryTableCatchVector catches;
  // The pending exception for the try's landing pad.
  MDefinition* pendingException;
  // The pending exception's tag for the try's landing pad.
  MDefinition* pendingExceptionTag;
  // Whether this try is in the body and should catch any thrown exception.
  bool inBody;

  TryControl()
      : pendingException(nullptr),
        pendingExceptionTag(nullptr),
        inBody(false) {}

  // Reset the try control for when it is cached in FunctionCompiler.
  void reset() {
    landingPadPatches.clearAndFree();
    catches.clearAndFree();
    inBody = false;
  }
};
using UniqueTryControl = UniquePtr<TryControl>;
using VectorUniqueTryControl = Vector<UniqueTryControl, 2, SystemAllocPolicy>;

struct ControlFlowPatch {
  MControlInstruction* ins;
  uint32_t index;
  ControlFlowPatch(MControlInstruction* ins, uint32_t index)
      : ins(ins), index(index) {}
};

using ControlFlowPatchVector = Vector<ControlFlowPatch, 0, SystemAllocPolicy>;

struct PendingBlockTarget {
  ControlFlowPatchVector patches;
  BranchHint hint = BranchHint::Invalid;
};

using PendingBlockTargetVector =
    Vector<PendingBlockTarget, 0, SystemAllocPolicy>;

// Inlined functions accumulate all returns to be bound to a caller function
// after compilation is finished.
struct PendingInlineReturn {
  PendingInlineReturn(MGoto* jump, DefVector&& results)
      : jump(jump), results(std::move(results)) {}

  MGoto* jump;
  DefVector results;
};

using PendingInlineReturnVector =
    Vector<PendingInlineReturn, 1, SystemAllocPolicy>;

// CallCompileState describes a call that is being compiled.
struct CallCompileState {
  // A generator object that is passed each argument as it is compiled.
  ABIArgGenerator abi;

  // Whether we pass FP values through GPRs or FPRs.
  bool hardFP = true;

  // The ABI we are using for this call.
  ABIKind abiKind;

  // Accumulates the register arguments while compiling arguments.
  MWasmCallBase::Args regArgs;

  // Reserved argument for passing Instance* to builtin instance method calls.
  ABIArg instanceArg;

  // The stack area in which the callee will write stack return values, or
  // nullptr if no stack results.
  MWasmStackResultArea* stackResultArea = nullptr;

  // Indicates that the call is a return/tail call.
  bool returnCall = false;

  // The landing pad patches for the nearest enclosing try-catch. This is
  // non-null iff the call is catchable.
  ControlInstructionVector* tryLandingPadPatches = nullptr;

  // The index of the try note for a catchable call.
  uint32_t tryNoteIndex = UINT32_MAX;

  // The block to take for fallthrough execution for a catchable call.
  MBasicBlock* fallthroughBlock = nullptr;

  // The block to take for exceptional execution for a catchable call.
  MBasicBlock* prePadBlock = nullptr;

  explicit CallCompileState(ABIKind abiKind) : abi(abiKind), abiKind(abiKind) {
    if (abiKind == ABIKind::System) {
      // The system ABI follows the hardFP setting on ARM32.
#if defined(JS_CODEGEN_ARM)
      hardFP = ARMFlags::UseHardFpABI();
      abi.setUseHardFp(hardFP);
#endif
    } else {
#if defined(JS_CODEGEN_ARM)
      MOZ_ASSERT(hardFP, "The WASM ABI passes FP arguments in registers");
#endif
    }
  }

  bool isCatchable() const { return tryLandingPadPatches != nullptr; }
};

struct Control {
  MBasicBlock* block;
  UniqueTryControl tryControl;

  Control() : block(nullptr), tryControl(nullptr) {}
  Control(Control&&) = default;
  Control(const Control&) = delete;
};

struct IonCompilePolicy {
  // We store SSA definitions in the value stack.
  using Value = MDefinition*;
  using ValueVector = DefVector;

  // We store loop headers and then/else blocks in the control flow stack.
  // In the case of try-catch control blocks, we collect additional information
  // regarding the possible paths from throws and calls to a landing pad, as
  // well as information on the landing pad's handlers (its catches).
  using ControlItem = Control;
};

using IonOpIter = OpIter<IonCompilePolicy>;

// Statistics for inlining (at all depths) into the root function.
struct InliningStats {
  size_t inlinedDirectBytecodeSize = 0;   // sum of sizes of inlinees
  size_t inlinedDirectFunctions = 0;      // number of inlinees
  size_t inlinedCallRefBytecodeSize = 0;  // sum of sizes of inlinees
  size_t inlinedCallRefFunctions = 0;     // number of inlinees
  bool largeFunctionBackoff = false;      // did large function backoff happen?
};

// Encapsulates the generation of MIR for a wasm function and any functions
// that become inlined into it.
class RootCompiler {
  const CompilerEnvironment& compilerEnv_;
  const CodeMetadata& codeMeta_;
  const CodeTailMetadata* codeTailMeta_;

  const ValTypeVector& locals_;
  const FuncCompileInput& func_;
  Decoder& decoder_;
  FeatureUsage observedFeatures_;

  CompileInfo compileInfo_;
  const JitCompileOptions options_;
  TempAllocator& alloc_;
  MIRGraph mirGraph_;
  MIRGenerator mirGen_;

  // The current loop depth we're generating inside of. This includes all
  // callee functions when we're generating an inlined function, and so it
  // lives here on the root compiler.
  uint32_t loopDepth_;

  // The current stack of bytecode offsets of the caller functions of the
  // function currently being inlined.
  BytecodeOffsetVector inlinedCallerOffsets_;
  InlinedCallerOffsetIndex inlinedCallerOffsetsIndex_;

  // Compilation statistics for this function.
  CompileStats funcStats_;

  // Accumulated inlining statistics for this function.
  InliningStats inliningStats_;
  // The remaining inlining budget, in terms of bytecode bytes. This may go
  // negative and so is signed.
  int64_t localInliningBudget_;

  // All jit::CompileInfo objects created during this compilation. This must
  // be kept alive for as long as the MIR graph is alive.
  UniqueCompileInfoVector compileInfos_;

  // Cache of TryControl to minimize heap allocations.
  VectorUniqueTryControl tryControlCache_;

  // Reference to masm.tryNotes()
  wasm::TryNoteVector& tryNotes_;

  // Reference to masm.inliningContext()
  wasm::InliningContext& inliningContext_;

 public:
  RootCompiler(const CompilerEnvironment& compilerEnv,
               const CodeMetadata& codeMeta,
               const CodeTailMetadata* codeTailMeta, TempAllocator& alloc,
               const ValTypeVector& locals, const FuncCompileInput& func,
               Decoder& decoder, wasm::TryNoteVector& tryNotes,
               wasm::InliningContext& inliningContext)
      : compilerEnv_(compilerEnv),
        codeMeta_(codeMeta),
        codeTailMeta_(codeTailMeta),
        locals_(locals),
        func_(func),
        decoder_(decoder),
        observedFeatures_(FeatureUsage::None),
        compileInfo_(locals.length()),
        alloc_(alloc),
        mirGraph_(&alloc),
        mirGen_(nullptr, options_, &alloc_, &mirGraph_, &compileInfo_,
                IonOptimizations.get(OptimizationLevel::Wasm), &codeMeta),
        loopDepth_(0),
        localInliningBudget_(0),
        tryNotes_(tryNotes),
        inliningContext_(inliningContext) {}

  const CompilerEnvironment& compilerEnv() const { return compilerEnv_; }
  const CodeMetadata& codeMeta() const { return codeMeta_; }
  const CodeTailMetadata* codeTailMeta() const { return codeTailMeta_; }
  const FuncCompileInput& func() const { return func_; }
  TempAllocator& alloc() { return alloc_; }
  MIRGraph& mirGraph() { return mirGraph_; }
  MIRGenerator& mirGen() { return mirGen_; }
  int64_t inliningBudget() const { return localInliningBudget_; }
  FeatureUsage observedFeatures() const { return observedFeatures_; }
  const CompileStats& funcStats() const { return funcStats_; }
  void noteLargeFunctionBackoffWasApplied() {
    inliningStats_.largeFunctionBackoff = true;
  }

  uint32_t loopDepth() const { return loopDepth_; }
  void startLoop() { loopDepth_++; }
  void closeLoop() { loopDepth_--; }

  [[nodiscard]] bool generate();

  InlinedCallerOffsetIndex inlinedCallerOffsetsIndex() const {
    return inlinedCallerOffsetsIndex_;
  }

  // Add a compile info for an inlined function. This keeps the inlined
  // function's compile info alive for the outermost function's
  // compilation.
  [[nodiscard]] CompileInfo* startInlineCall(
      uint32_t callerFuncIndex, BytecodeOffset callerOffset,
      uint32_t calleeFuncIndex, uint32_t numLocals, size_t inlineeBytecodeSize,
      InliningHeuristics::CallKind callKind);
  void finishInlineCall();

  // Add a try note and return the index.
  [[nodiscard]] bool addTryNote(uint32_t* tryNoteIndex) {
    if (!tryNotes_.append(wasm::TryNote())) {
      return false;
    }
    *tryNoteIndex = tryNotes_.length() - 1;
    return true;
  }

  // Try to get a free TryControl from the cache, or allocate a new one.
  [[nodiscard]] UniqueTryControl newTryControl() {
    if (tryControlCache_.empty()) {
      return UniqueTryControl(js_new<TryControl>());
    }
    UniqueTryControl tryControl = std::move(tryControlCache_.back());
    tryControlCache_.popBack();
    return tryControl;
  }

  // Release the TryControl to the cache.
  void freeTryControl(UniqueTryControl&& tryControl) {
    // Ensure that it's in a consistent state
    tryControl->reset();
    // Ignore any OOM, as we'll fail later
    (void)tryControlCache_.append(std::move(tryControl));
  }
};

// Encapsulates the generation of MIR for a single function in a wasm module.
class FunctionCompiler {
  // The root function compiler we are being compiled within.
  RootCompiler& rootCompiler_;

  // The caller function compiler, if any, that we are being inlined into.
  // Note that `inliningDepth_` is zero for the first inlinee, one for the
  // second inlinee, etc.
  const FunctionCompiler* callerCompiler_;
  const uint32_t inliningDepth_;

  // Information about this function's bytecode and parsing state
  IonOpIter iter_;
  uint32_t functionBodyOffset_;
  const FuncCompileInput& func_;
  const ValTypeVector& locals_;
  size_t numCallRefs_;
  size_t numAllocSites_;

  // CompileInfo for compiling the MIR for this function. Allocated inside of
  // RootCompiler::compileInfos, and kept alive for the duration of the
  // total compilation.
  const jit::CompileInfo& info_;

  MBasicBlock* curBlock_;
  uint32_t maxStackArgBytes_;

  // When generating a forward branch we haven't created the basic block that
  // the branch needs to target. We handle this by accumulating all the branch
  // instructions that want to target a block we have not yet created into
  // `pendingBlocks_` and then patching them in `bindBranches`.
  //
  // For performance reasons we only grow `pendingBlocks_` as needed, never
  // shrink it. So the length of the vector has no relation to the current
  // nesting depth of wasm blocks. We use `pendingBlockDepth_` to track the
  // current wasm block depth. We assert that all entries beyond the current
  // block depth are empty.
  uint32_t pendingBlockDepth_;
  PendingBlockTargetVector pendingBlocks_;
  // Control flow patches for exceptions that are caught without a landing
  // pad they can directly jump to. This happens when either:
  //  (1) `delegate` targets the function body label.
  //  (2) A `try` ends without any cases, and there is no enclosing `try`.
  //  (3) There is no `try` in this function, but a caller function (when
  //      inlining) has a `try`.
  //
  // These exceptions will be rethrown using `emitBodyRethrowPad`.
  ControlInstructionVector bodyRethrowPadPatches_;
  // A vector of the returns in this function for use when we're being inlined
  // into another function.
  PendingInlineReturnVector pendingInlineReturns_;
  // A block that all uncaught exceptions in this function will jump to. The
  // inline caller will link this to the nearest enclosing catch handler.
  MBasicBlock* pendingInlineCatchBlock_;

  // Instance pointer argument to the current function.
  MWasmParameter* instancePointer_;
  MWasmParameter* stackResultPointer_;

 public:
  // Construct a FunctionCompiler for the root function of a compilation
  FunctionCompiler(RootCompiler& rootCompiler, Decoder& decoder,
                   const FuncCompileInput& func, const ValTypeVector& locals,
                   const CompileInfo& compileInfo)
      : rootCompiler_(rootCompiler),
        callerCompiler_(nullptr),
        inliningDepth_(0),
        iter_(rootCompiler.codeMeta(), decoder, locals),
        functionBodyOffset_(decoder.beginOffset()),
        func_(func),
        locals_(locals),
        numCallRefs_(0),
        numAllocSites_(0),
        info_(compileInfo),
        curBlock_(nullptr),
        maxStackArgBytes_(0),
        pendingBlockDepth_(0),
        pendingInlineCatchBlock_(nullptr),
        instancePointer_(nullptr),
        stackResultPointer_(nullptr) {}

  // Construct a FunctionCompiler for an inlined callee of a compilation
  FunctionCompiler(const FunctionCompiler* callerCompiler, Decoder& decoder,
                   const FuncCompileInput& func, const ValTypeVector& locals,
                   const CompileInfo& compileInfo)
      : rootCompiler_(callerCompiler->rootCompiler_),
        callerCompiler_(callerCompiler),
        inliningDepth_(callerCompiler_->inliningDepth() + 1),
        iter_(rootCompiler_.codeMeta(), decoder, locals),
        functionBodyOffset_(decoder.beginOffset()),
        func_(func),
        locals_(locals),
        numCallRefs_(0),
        numAllocSites_(0),
        info_(compileInfo),
        curBlock_(nullptr),
        maxStackArgBytes_(0),
        pendingBlockDepth_(0),
        pendingInlineCatchBlock_(nullptr),
        instancePointer_(callerCompiler_->instancePointer_),
        stackResultPointer_(nullptr) {}

  RootCompiler& rootCompiler() { return rootCompiler_; }
  const CodeMetadata& codeMeta() const { return rootCompiler_.codeMeta(); }
  const CodeTailMetadata* codeTailMeta() const {
    return rootCompiler_.codeTailMeta();
  }

  IonOpIter& iter() { return iter_; }
  uint32_t relativeBytecodeOffset() {
    return readBytecodeOffset() - functionBodyOffset_;
  }
  TempAllocator& alloc() const { return rootCompiler_.alloc(); }
  // FIXME(1401675): Replace with BlockType.
  uint32_t funcIndex() const { return func_.index; }
  const FuncType& funcType() const {
    return codeMeta().getFuncType(func_.index);
  }

  bool isInlined() const { return callerCompiler_ != nullptr; }
  uint32_t inliningDepth() const { return inliningDepth_; }

  MBasicBlock* getCurBlock() const { return curBlock_; }
  BytecodeOffset bytecodeOffset() const { return iter_.bytecodeOffset(); }
  CallSiteDesc callSiteDesc(CallSiteKind kind) {
    return CallSiteDesc(bytecodeOffset().offset(),
                        rootCompiler_.inlinedCallerOffsetsIndex(), kind);
  }
  TrapSiteDesc trapSiteDesc() {
    return TrapSiteDesc(wasm::BytecodeOffset(bytecodeOffset()),
                        rootCompiler_.inlinedCallerOffsetsIndex());
  }
  TrapSiteDesc trapSiteDescWithCallSiteLineNumber() {
    return TrapSiteDesc(wasm::BytecodeOffset(readBytecodeOffset()),
                        rootCompiler_.inlinedCallerOffsetsIndex());
  }
  FeatureUsage featureUsage() const { return iter_.featureUsage(); }

  [[nodiscard]] bool initRoot() {
    // We are not being inlined into something
    MOZ_ASSERT(!callerCompiler_);

    // Prepare the entry block for MIR generation:

    const FuncType& ft = funcType();
    const ArgTypeVector args(ft);

    if (!mirGen().ensureBallast()) {
      return false;
    }
    if (!newBlock(/* prev */ nullptr, &curBlock_)) {
      return false;
    }

    for (ABIArgIter i(args, ABIKind::Wasm); !i.done(); i++) {
      MaybeRefType argRefType;
      if (!args.isSyntheticStackResultPointerArg(i.index())) {
        ValType argType = ft.arg(i.index());
        argRefType = argType.isRefType() ? MaybeRefType(argType.refType())
                                         : MaybeRefType();
      }

      MWasmParameter* ins =
          MWasmParameter::New(alloc(), *i, i.mirType(), argRefType);
      curBlock_->add(ins);
      if (args.isSyntheticStackResultPointerArg(i.index())) {
        MOZ_ASSERT(stackResultPointer_ == nullptr);
        stackResultPointer_ = ins;
      } else {
        curBlock_->initSlot(info().localSlot(args.naturalIndex(i.index())),
                            ins);
      }
      if (!mirGen().ensureBallast()) {
        return false;
      }
    }

    // Set up a parameter that receives the hidden instance pointer argument.
    instancePointer_ =
        MWasmParameter::New(alloc(), ABIArg(InstanceReg), MIRType::Pointer);
    curBlock_->add(instancePointer_);
    if (!mirGen().ensureBallast()) {
      return false;
    }

    for (size_t i = args.lengthWithoutStackResults(); i < locals_.length();
         i++) {
      ValType slotValType = locals_[i];
#ifndef ENABLE_WASM_SIMD
      if (slotValType == ValType::V128) {
        return iter().fail("Ion has no SIMD support yet");
      }
#endif
      MDefinition* zero = constantZeroOfValType(slotValType);
      curBlock_->initSlot(info().localSlot(i), zero);
      if (!mirGen().ensureBallast()) {
        return false;
      }
    }

    return true;
  }

  [[nodiscard]] bool initInline(const DefVector& argValues) {
    // "This is an inlined-callee FunctionCompiler"
    MOZ_ASSERT(callerCompiler_);

    // Prepare the entry block for MIR generation:
    if (!mirGen().ensureBallast()) {
      return false;
    }
    if (!newBlock(nullptr, &curBlock_)) {
      return false;
    }

    MBasicBlock* pred = callerCompiler_->curBlock_;
    pred->end(MGoto::New(alloc(), curBlock_));
    if (!curBlock_->addPredecessorWithoutPhis(pred)) {
      return false;
    }

    // Set up args slots to point to passed argument values
    const FuncType& type = funcType();
    for (uint32_t argIndex = 0; argIndex < type.args().length(); argIndex++) {
      curBlock_->initSlot(info().localSlot(argIndex), argValues[argIndex]);
    }

    // Set up a parameter that receives the hidden instance pointer argument.
    instancePointer_ = callerCompiler_->instancePointer_;

    // Initialize all local slots to zero value
    for (size_t i = type.args().length(); i < locals_.length(); i++) {
      ValType slotValType = locals_[i];
#ifndef ENABLE_WASM_SIMD
      if (slotValType == ValType::V128) {
        return iter().fail("Ion has no SIMD support yet");
      }
#endif
      MDefinition* zero = constantZeroOfValType(slotValType);
      curBlock_->initSlot(info().localSlot(i), zero);
      if (!mirGen().ensureBallast()) {
        return false;
      }
    }

    return true;
  }

  void finish() {
    mirGen().accumulateWasmMaxStackArgBytes(maxStackArgBytes_);

    MOZ_ASSERT(pendingBlockDepth_ == 0);
#ifdef DEBUG
    for (PendingBlockTarget& targets : pendingBlocks_) {
      MOZ_ASSERT(targets.patches.empty());
    }
#endif
    MOZ_ASSERT(inDeadCode());
    MOZ_ASSERT(done());
    MOZ_ASSERT_IF(
        compilerEnv().mode() == CompileMode::LazyTiering,
        codeTailMeta()->getFuncDefCallRefs(funcIndex()).length == numCallRefs_);
    MOZ_ASSERT_IF(codeTailMeta(),
                  codeTailMeta()->getFuncDefAllocSites(funcIndex()).length ==
                      numAllocSites_);
    MOZ_ASSERT_IF(!isInlined(),
                  pendingInlineReturns_.empty() && !pendingInlineCatchBlock_);
    MOZ_ASSERT(bodyRethrowPadPatches_.empty());
  }

  /************************* Read-only interface (after local scope setup) */

  MIRGenerator& mirGen() const { return rootCompiler_.mirGen(); }
  MIRGraph& mirGraph() const { return rootCompiler_.mirGraph(); }
  const CompileInfo& info() const { return info_; }
  const CompilerEnvironment& compilerEnv() const {
    return rootCompiler_.compilerEnv();
  }

  MDefinition* getLocalDef(unsigned slot) {
    if (inDeadCode()) {
      return nullptr;
    }
    return curBlock_->getSlot(info().localSlot(slot));
  }

  const ValTypeVector& locals() const { return locals_; }

  /*********************************************************** Constants ***/

  MDefinition* constantF32(float f) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* cst = MWasmFloatConstant::NewFloat32(alloc(), f);
    curBlock_->add(cst);
    return cst;
  }
  // Hide all other overloads, to guarantee no implicit argument conversion.
  template <typename T>
  MDefinition* constantF32(T) = delete;

  MDefinition* constantF64(double d) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* cst = MWasmFloatConstant::NewDouble(alloc(), d);
    curBlock_->add(cst);
    return cst;
  }
  template <typename T>
  MDefinition* constantF64(T) = delete;

  MDefinition* constantI32(int32_t i) {
    if (inDeadCode()) {
      return nullptr;
    }
    MConstant* constant = MConstant::NewInt32(alloc(), i);
    curBlock_->add(constant);
    return constant;
  }
  template <typename T>
  MDefinition* constantI32(T) = delete;

  MDefinition* constantI64(int64_t i) {
    if (inDeadCode()) {
      return nullptr;
    }
    MConstant* constant = MConstant::NewInt64(alloc(), i);
    curBlock_->add(constant);
    return constant;
  }
  template <typename T>
  MDefinition* constantI64(T) = delete;

  // Produce an MConstant of the machine's target int type (Int32 or Int64).
  MDefinition* constantTargetWord(intptr_t n) {
    return targetIs64Bit() ? constantI64(int64_t(n)) : constantI32(int32_t(n));
  }
  template <typename T>
  MDefinition* constantTargetWord(T) = delete;

#ifdef ENABLE_WASM_SIMD
  MDefinition* constantV128(V128 v) {
    if (inDeadCode()) {
      return nullptr;
    }
    MWasmFloatConstant* constant = MWasmFloatConstant::NewSimd128(
        alloc(), SimdConstant::CreateSimd128((int8_t*)v.bytes));
    curBlock_->add(constant);
    return constant;
  }
  template <typename T>
  MDefinition* constantV128(T) = delete;
#endif

  MDefinition* constantNullRef(MaybeRefType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    // MConstant has a lot of baggage so we don't use that here.
    MWasmNullConstant* constant = MWasmNullConstant::New(alloc(), type);
    curBlock_->add(constant);
    return constant;
  }

  // Produce a zero constant for the specified ValType.
  MDefinition* constantZeroOfValType(ValType valType) {
    switch (valType.kind()) {
      case ValType::I32:
        return constantI32(0);
      case ValType::I64:
        return constantI64(int64_t(0));
#ifdef ENABLE_WASM_SIMD
      case ValType::V128:
        return constantV128(V128(0));
#endif
      case ValType::F32:
        return constantF32(0.0f);
      case ValType::F64:
        return constantF64(0.0);
      case ValType::Ref:
        return constantNullRef(MaybeRefType(valType.refType()));
      default:
        MOZ_CRASH();
    }
  }

  /***************************** Code generation (after local scope setup) */

  void fence() {
    if (inDeadCode()) {
      return;
    }
    MWasmFence* ins = MWasmFence::New(alloc());
    curBlock_->add(ins);
  }

  template <class T>
  MDefinition* unary(MDefinition* op) {
    if (inDeadCode()) {
      return nullptr;
    }
    T* ins = T::New(alloc(), op);
    curBlock_->add(ins);
    return ins;
  }

  template <class T>
  MDefinition* unary(MDefinition* op, MIRType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    T* ins = T::New(alloc(), op, type);
    curBlock_->add(ins);
    return ins;
  }

  template <class T>
  MDefinition* binary(MDefinition* lhs, MDefinition* rhs) {
    if (inDeadCode()) {
      return nullptr;
    }
    T* ins = T::New(alloc(), lhs, rhs);
    curBlock_->add(ins);
    return ins;
  }

  template <class T>
  MDefinition* binary(MDefinition* lhs, MDefinition* rhs, MIRType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    T* ins = T::New(alloc(), lhs, rhs, type);
    curBlock_->add(ins);
    return ins;
  }

  template <class T>
  MDefinition* binary(MDefinition* lhs, MDefinition* rhs, MIRType type,
                      MWasmBinaryBitwise::SubOpcode subOpc) {
    if (inDeadCode()) {
      return nullptr;
    }
    T* ins = T::New(alloc(), lhs, rhs, type, subOpc);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* ursh(MDefinition* lhs, MDefinition* rhs, MIRType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MUrsh::NewWasm(alloc(), lhs, rhs, type);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* add(MDefinition* lhs, MDefinition* rhs, MIRType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MAdd::NewWasm(alloc(), lhs, rhs, type);
    curBlock_->add(ins);
    return ins;
  }

  bool mustPreserveNaN(MIRType type) { return IsFloatingPointType(type); }

  MDefinition* sub(MDefinition* lhs, MDefinition* rhs, MIRType type) {
    if (inDeadCode()) {
      return nullptr;
    }

    // wasm can't fold x - 0.0 because of NaN with custom payloads.
    MSub* ins = MSub::NewWasm(alloc(), lhs, rhs, type, mustPreserveNaN(type));
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* nearbyInt(MDefinition* input, RoundingMode roundingMode) {
    if (inDeadCode()) {
      return nullptr;
    }

    auto* ins = MNearbyInt::New(alloc(), input, input->type(), roundingMode);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* minMax(MDefinition* lhs, MDefinition* rhs, MIRType type,
                      bool isMax) {
    if (inDeadCode()) {
      return nullptr;
    }

    if (mustPreserveNaN(type)) {
      // Convert signaling NaN to quiet NaNs.
      MDefinition* zero = constantZeroOfValType(ValType::fromMIRType(type));
      lhs = sub(lhs, zero, type);
      rhs = sub(rhs, zero, type);
    }

    MMinMax* ins = MMinMax::NewWasm(alloc(), lhs, rhs, type, isMax);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* mul(MDefinition* lhs, MDefinition* rhs, MIRType type,
                   MMul::Mode mode) {
    if (inDeadCode()) {
      return nullptr;
    }

    // wasm can't fold x * 1.0 because of NaN with custom payloads.
    auto* ins =
        MMul::NewWasm(alloc(), lhs, rhs, type, mode, mustPreserveNaN(type));
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type,
                   bool unsignd) {
    if (inDeadCode()) {
      return nullptr;
    }
    if (!unsignd && type == MIRType::Int32) {
      // Enforce the signedness of the operation by coercing the operands
      // to signed.  Otherwise, operands that "look" unsigned to Ion but
      // are not unsigned to Baldr (eg, unsigned right shifts) may lead to
      // the operation being executed unsigned.  Applies to mod() as well.
      //
      // Do this for Int32 only since Int64 is not subject to the same
      // issues.
      //
      // Note the offsets passed to MWasmBuiltinTruncateToInt32 are wrong here,
      // but it doesn't matter: they're not codegen'd to calls since inputs
      // already are int32.
      auto* lhs2 = createTruncateToInt32(lhs);
      curBlock_->add(lhs2);
      lhs = lhs2;
      auto* rhs2 = createTruncateToInt32(rhs);
      curBlock_->add(rhs2);
      rhs = rhs2;
    }

    // For x86 and arm we implement i64 div via c++ builtin.
    // A call to c++ builtin requires instance pointer.
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
    if (type == MIRType::Int64) {
      auto* ins =
          MWasmBuiltinDivI64::New(alloc(), lhs, rhs, instancePointer_, unsignd,
                                  /*trapOnError=*/true, trapSiteDesc());
      curBlock_->add(ins);
      return ins;
    }
#endif

    auto* ins =
        MDiv::New(alloc(), lhs, rhs, type, unsignd, /*trapOnError=*/true,
                  trapSiteDesc(), mustPreserveNaN(type));
    curBlock_->add(ins);
    return ins;
  }

  MInstruction* createTruncateToInt32(MDefinition* op) {
    if (op->type() == MIRType::Double || op->type() == MIRType::Float32) {
      return MWasmBuiltinTruncateToInt32::New(alloc(), op, instancePointer_);
    }

    return MTruncateToInt32::New(alloc(), op);
  }

  MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type,
                   bool unsignd) {
    if (inDeadCode()) {
      return nullptr;
    }
    if (!unsignd && type == MIRType::Int32) {
      // See block comment in div().
      auto* lhs2 = createTruncateToInt32(lhs);
      curBlock_->add(lhs2);
      lhs = lhs2;
      auto* rhs2 = createTruncateToInt32(rhs);
      curBlock_->add(rhs2);
      rhs = rhs2;
    }

    // For x86 and arm we implement i64 mod via c++ builtin.
    // A call to c++ builtin requires instance pointer.
#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM)
    if (type == MIRType::Int64) {
      auto* ins =
          MWasmBuiltinModI64::New(alloc(), lhs, rhs, instancePointer_, unsignd,
                                  /*trapOnError=*/true, trapSiteDesc());
      curBlock_->add(ins);
      return ins;
    }
#endif

    // Should be handled separately because we call BuiltinThunk for this case
    // and so, need to add the dependency from instancePointer.
    if (type == MIRType::Double) {
      auto* ins = MWasmBuiltinModD::New(alloc(), lhs, rhs, instancePointer_,
                                        type, bytecodeOffset());
      curBlock_->add(ins);
      return ins;
    }

    auto* ins = MMod::New(alloc(), lhs, rhs, type, unsignd,
                          /*trapOnError=*/true, trapSiteDesc());
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* bitnot(MDefinition* op, MIRType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MBitNot::New(alloc(), op, type);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* select(MDefinition* trueExpr, MDefinition* falseExpr,
                      MDefinition* condExpr) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MWasmSelect::New(alloc(), trueExpr, falseExpr, condExpr);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* extendI32(MDefinition* op, bool isUnsigned) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MExtendInt32ToInt64::New(alloc(), op, isUnsigned);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* signExtend(MDefinition* op, uint32_t srcSize,
                          uint32_t targetSize) {
    if (inDeadCode()) {
      return nullptr;
    }
    MInstruction* ins;
    switch (targetSize) {
      case 4: {
        MSignExtendInt32::Mode mode;
        switch (srcSize) {
          case 1:
            mode = MSignExtendInt32::Byte;
            break;
          case 2:
            mode = MSignExtendInt32::Half;
            break;
          default:
            MOZ_CRASH("Bad sign extension");
        }
        ins = MSignExtendInt32::New(alloc(), op, mode);
        break;
      }
      case 8: {
        MSignExtendInt64::Mode mode;
        switch (srcSize) {
          case 1:
            mode = MSignExtendInt64::Byte;
            break;
          case 2:
            mode = MSignExtendInt64::Half;
            break;
          case 4:
            mode = MSignExtendInt64::Word;
            break;
          default:
            MOZ_CRASH("Bad sign extension");
        }
        ins = MSignExtendInt64::New(alloc(), op, mode);
        break;
      }
      default: {
        MOZ_CRASH("Bad sign extension");
      }
    }
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* wrapI32(MDefinition* op) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MWrapInt64ToInt32::New(alloc(), op, /*bottomHalf=*/true);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* convertI64ToFloatingPoint(MDefinition* op, MIRType type,
                                         bool isUnsigned) {
    if (inDeadCode()) {
      return nullptr;
    }
#if defined(JS_CODEGEN_ARM)
    auto* ins = MBuiltinInt64ToFloatingPoint::New(
        alloc(), op, instancePointer_, type, bytecodeOffset(), isUnsigned);
#else
    auto* ins = MInt64ToFloatingPoint::New(alloc(), op, type, bytecodeOffset(),
                                           isUnsigned);
#endif
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* rotate(MDefinition* input, MDefinition* count, MIRType type,
                      bool left) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MRotate::New(alloc(), input, count, type, left);
    curBlock_->add(ins);
    return ins;
  }

  template <class T>
  MDefinition* truncate(MDefinition* op, TruncFlags flags) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = T::New(alloc(), op, flags, trapSiteDesc());
    curBlock_->add(ins);
    return ins;
  }

#if defined(JS_CODEGEN_ARM)
  MDefinition* truncateWithInstance(MDefinition* op, TruncFlags flags) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MWasmBuiltinTruncateToInt64::New(alloc(), op, instancePointer_,
                                                 flags, trapSiteDesc());
    curBlock_->add(ins);
    return ins;
  }
#endif

  MDefinition* compare(MDefinition* lhs, MDefinition* rhs, JSOp op,
                       MCompare::CompareType type) {
    if (inDeadCode()) {
      return nullptr;
    }
    auto* ins = MCompare::NewWasm(alloc(), lhs, rhs, op, type);
    curBlock_->add(ins);
    return ins;
  }

  void assign(unsigned slot, MDefinition* def) {
    if (inDeadCode()) {
      return;
    }
    curBlock_->setSlot(info().localSlot(slot), def);
  }

  MDefinition* compareIsNull(MDefinition* ref, JSOp compareOp) {
    MDefinition* nullVal = constantNullRef(MaybeRefType());
    if (!nullVal) {
      return nullptr;
    }
    return compare(ref, nullVal, compareOp, MCompare::Compare_WasmAnyRef);
  }

  [[nodiscard]] MDefinition* refAsNonNull(MDefinition* ref) {
    MOZ_ASSERT(!inDeadCode());
    auto* ins = MWasmRefAsNonNull::New(alloc(), ref, trapSiteDesc());
    if (!ins) {
      return nullptr;
    }
    curBlock_->add(ins);
    return ins;
  }

  [[nodiscard]] bool brOnNull(uint32_t relativeDepth, const DefVector& values,
                              const ResultType& type, MDefinition* condition,
                              BranchHint branchHint) {
    if (inDeadCode()) {
      return true;
    }

    MBasicBlock* fallthroughBlock = nullptr;
    if (!newBlock(curBlock_, &fallthroughBlock)) {
      return false;
    }

    MDefinition* check = compareIsNull(condition, JSOp::Eq);
    if (!check) {
      return false;
    }
    MTest* test = MTest::New(alloc(), check, nullptr, fallthroughBlock);
    if (!test || !addControlFlowPatch(test, relativeDepth,
                                      MTest::TrueBranchIndex, branchHint)) {
      return false;
    }

    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(test);
    curBlock_ = fallthroughBlock;
    return true;
  }

  [[nodiscard]] bool brOnNonNull(uint32_t relativeDepth,
                                 const DefVector& values,
                                 const ResultType& type, MDefinition* condition,
                                 BranchHint branchHint) {
    if (inDeadCode()) {
      return true;
    }

    MBasicBlock* fallthroughBlock = nullptr;
    if (!newBlock(curBlock_, &fallthroughBlock)) {
      return false;
    }

    MDefinition* check = compareIsNull(condition, JSOp::Ne);
    if (!check) {
      return false;
    }
    MTest* test = MTest::New(alloc(), check, nullptr, fallthroughBlock);
    if (!test || !addControlFlowPatch(test, relativeDepth,
                                      MTest::TrueBranchIndex, branchHint)) {
      return false;
    }

    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(test);
    curBlock_ = fallthroughBlock;
    return true;
  }

  MDefinition* refI31(MDefinition* input) {
    auto* ins = MWasmNewI31Ref::New(alloc(), input);
    curBlock_->add(ins);
    return ins;
  }

  MDefinition* i31Get(MDefinition* input, FieldWideningOp wideningOp) {
    auto* ins = MWasmI31RefGet::New(alloc(), input, wideningOp);
    curBlock_->add(ins);
    return ins;
  }

#ifdef ENABLE_WASM_SIMD
  // About Wasm SIMD as supported by Ion:
  //
  // The expectation is that Ion will only ever support SIMD on x86 and x64,
  // since ARMv7 will cease to be a tier-1 platform soon, and MIPS64 will never
  // implement SIMD.
  //
  // The division of the operations into MIR nodes reflects that expectation,
  // and is a good fit for x86/x64.  Should the expectation change we'll
  // possibly want to re-architect the SIMD support to be a little more general.
  //
  // Most SIMD operations map directly to a single MIR node that ultimately ends
  // up being expanded in the macroassembler.
  //
  // Some SIMD operations that do have a complete macroassembler expansion are
  // open-coded into multiple MIR nodes here; in some cases that's just
  // convenience, in other cases it may also allow them to benefit from Ion
  // optimizations.  The reason for the expansions will be documented by a
  // comment.

  // (v128,v128) -> v128 effect-free binary operations
  MDefinition* binarySimd128(MDefinition* lhs, MDefinition* rhs,
                             bool commutative, SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(lhs->type() == MIRType::Simd128 &&
               rhs->type() == MIRType::Simd128);

    auto* ins = MWasmBinarySimd128::New(alloc(), lhs, rhs, commutative, op);
    curBlock_->add(ins);
    return ins;
  }

  // (v128,i32) -> v128 effect-free shift operations
  MDefinition* shiftSimd128(MDefinition* lhs, MDefinition* rhs, SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(lhs->type() == MIRType::Simd128 &&
               rhs->type() == MIRType::Int32);

    int32_t maskBits;
    if (MacroAssembler::MustMaskShiftCountSimd128(op, &maskBits)) {
      MDefinition* mask = constantI32(maskBits);
      auto* rhs2 = MBitAnd::New(alloc(), rhs, mask, MIRType::Int32);
      curBlock_->add(rhs2);
      rhs = rhs2;
    }

    auto* ins = MWasmShiftSimd128::New(alloc(), lhs, rhs, op);
    curBlock_->add(ins);
    return ins;
  }

  // (v128,scalar,imm) -> v128
  MDefinition* replaceLaneSimd128(MDefinition* lhs, MDefinition* rhs,
                                  uint32_t laneIndex, SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(lhs->type() == MIRType::Simd128);

    auto* ins = MWasmReplaceLaneSimd128::New(alloc(), lhs, rhs, laneIndex, op);
    curBlock_->add(ins);
    return ins;
  }

  // (scalar) -> v128 effect-free unary operations
  MDefinition* scalarToSimd128(MDefinition* src, SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    auto* ins = MWasmScalarToSimd128::New(alloc(), src, op);
    curBlock_->add(ins);
    return ins;
  }

  // (v128) -> v128 effect-free unary operations
  MDefinition* unarySimd128(MDefinition* src, SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(src->type() == MIRType::Simd128);
    auto* ins = MWasmUnarySimd128::New(alloc(), src, op);
    curBlock_->add(ins);
    return ins;
  }

  // (v128, imm) -> scalar effect-free unary operations
  MDefinition* reduceSimd128(MDefinition* src, SimdOp op, ValType outType,
                             uint32_t imm = 0) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(src->type() == MIRType::Simd128);
    auto* ins =
        MWasmReduceSimd128::New(alloc(), src, op, outType.toMIRType(), imm);
    curBlock_->add(ins);
    return ins;
  }

  // (v128, v128, v128) -> v128 effect-free operations
  MDefinition* ternarySimd128(MDefinition* v0, MDefinition* v1, MDefinition* v2,
                              SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(v0->type() == MIRType::Simd128 &&
               v1->type() == MIRType::Simd128 &&
               v2->type() == MIRType::Simd128);

    auto* ins = MWasmTernarySimd128::New(alloc(), v0, v1, v2, op);
    curBlock_->add(ins);
    return ins;
  }

  // (v128, v128, imm_v128) -> v128 effect-free operations
  MDefinition* shuffleSimd128(MDefinition* v1, MDefinition* v2, V128 control) {
    if (inDeadCode()) {
      return nullptr;
    }

    MOZ_ASSERT(v1->type() == MIRType::Simd128);
    MOZ_ASSERT(v2->type() == MIRType::Simd128);
    auto* ins = BuildWasmShuffleSimd128(
        alloc(), reinterpret_cast<int8_t*>(control.bytes), v1, v2);
    curBlock_->add(ins);
    return ins;
  }

  // Also see below for SIMD memory references

#endif  // ENABLE_WASM_SIMD

  /************************************************ Linear memory accesses */

  // For detailed information about memory accesses, see "Linear memory
  // addresses and bounds checking" in WasmMemory.cpp.

 private:
  // If the platform does not have a HeapReg, load the memory base from
  // instance.
  MDefinition* maybeLoadMemoryBase(uint32_t memoryIndex) {
#ifdef WASM_HAS_HEAPREG
    if (memoryIndex == 0) {
      return nullptr;
    }
#endif
    return memoryBase(memoryIndex);
  }

 public:
  // A value holding the memory base, whether that's HeapReg or some other
  // register.
  MDefinition* memoryBase(uint32_t memoryIndex) {
    AliasSet aliases = !codeMeta().memories[memoryIndex].canMovingGrow()
                           ? AliasSet::None()
                           : AliasSet::Load(AliasSet::WasmHeapMeta);
#ifdef WASM_HAS_HEAPREG
    if (memoryIndex == 0) {
      MWasmHeapReg* base = MWasmHeapReg::New(alloc(), aliases);
      curBlock_->add(base);
      return base;
    }
#endif
    uint32_t offset =
        memoryIndex == 0
            ? Instance::offsetOfMemory0Base()
            : (Instance::offsetInData(
                  codeMeta().offsetOfMemoryInstanceData(memoryIndex) +
                  offsetof(MemoryInstanceData, base)));
    MWasmLoadInstance* base = MWasmLoadInstance::New(
        alloc(), instancePointer_, offset, MIRType::Pointer, aliases);
    curBlock_->add(base);
    return base;
  }

 private:
  // If the bounds checking strategy requires it, load the bounds check limit
  // from the instance.
  MWasmLoadInstance* maybeLoadBoundsCheckLimit(uint32_t memoryIndex,
                                               MIRType type) {
    MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64);
    if (codeMeta().hugeMemoryEnabled(memoryIndex)) {
      return nullptr;
    }
    uint32_t offset =
        memoryIndex == 0
            ? Instance::offsetOfMemory0BoundsCheckLimit()
            : (Instance::offsetInData(
                  codeMeta().offsetOfMemoryInstanceData(memoryIndex) +
                  offsetof(MemoryInstanceData, boundsCheckLimit)));
    AliasSet aliases = !codeMeta().memories[memoryIndex].canMovingGrow()
                           ? AliasSet::None()
                           : AliasSet::Load(AliasSet::WasmHeapMeta);
    auto* load = MWasmLoadInstance::New(alloc(), instancePointer_, offset, type,
                                        aliases);
    curBlock_->add(load);
    return load;
  }

  // Return true if the access requires an alignment check.  If so, sets
  // *mustAdd to true if the offset must be added to the pointer before
  // checking.
  bool needAlignmentCheck(MemoryAccessDesc* access, MDefinition* base,
                          bool* mustAdd) {
    MOZ_ASSERT(!*mustAdd);

    if (!access->isAtomic()) {
      return false;
    }

    // If the EA is known and aligned it will need no checks.
    if (base->isConstant()) {
      // We only care about the low bits, so overflow is OK, as is chopping off
      // the high bits of an i64 pointer.
      uint32_t ptr = 0;
      if (isMem64(access->memoryIndex())) {
        ptr = uint32_t(base->toConstant()->toInt64());
      } else {
        ptr = base->toConstant()->toInt32();
      }
      if (((ptr + access->offset64()) & (access->byteSize() - 1)) == 0) {
        return false;
      }
    }

    // If the offset is aligned then the EA is just the pointer, for
    // the purposes of this check.
    *mustAdd = (access->offset64() & (access->byteSize() - 1)) != 0;
    return true;
  }

  // Fold a constant base into the offset and make the base 0, provided the
  // offset stays below the guard limit.  The reason for folding the base into
  // the offset rather than vice versa is that a small offset can be ignored
  // by both explicit bounds checking and bounds check elimination.
  void foldConstantPointer(MemoryAccessDesc* access, MDefinition** base) {
    PageSize pageSize = codeMeta().memories[access->memoryIndex()].pageSize();
    if (pageSize != PageSize::Standard) {
      return;
    }

    uint64_t offsetGuardLimit = GetMaxOffsetGuardLimit(
        codeMeta().hugeMemoryEnabled(access->memoryIndex()), pageSize);

    if ((*base)->isConstant()) {
      uint64_t basePtr = 0;
      if (isMem64(access->memoryIndex())) {
        basePtr = uint64_t((*base)->toConstant()->toInt64());
      } else {
        basePtr = uint64_t(int64_t((*base)->toConstant()->toInt32()));
      }

      uint64_t offset = access->offset64();
      if (offset < offsetGuardLimit && basePtr < offsetGuardLimit - offset) {
        offset += basePtr;
        access->setOffset32(uint32_t(offset));
        *base = isMem64(access->memoryIndex()) ? constantI64(int64_t(0))
                                               : constantI32(0);
      }
    }
  }

  // If the offset must be added because it is large or because the true EA must
  // be checked, compute the effective address, trapping on overflow.
  void maybeComputeEffectiveAddress(MemoryAccessDesc* access,
                                    MDefinition** base, bool mustAddOffset) {
    uint64_t offsetGuardLimit = GetMaxOffsetGuardLimit(
        codeMeta().hugeMemoryEnabled(access->memoryIndex()),
        codeMeta().memories[access->memoryIndex()].pageSize());

    if (access->offset64() >= offsetGuardLimit ||
        access->offset64() > UINT32_MAX || mustAddOffset ||
        !JitOptions.wasmFoldOffsets) {
      *base = computeEffectiveAddress(*base, access);
    }
  }

  MWasmLoadInstance* needBoundsCheck(uint32_t memoryIndex) {
    MOZ_RELEASE_ASSERT(codeMeta().memories[memoryIndex].pageSize() ==
                       PageSize::Standard);
#ifdef JS_64BIT
    // For 32-bit base pointers:
    //
    // If the bounds check uses the full 64 bits of the bounds check limit, then
    // the base pointer must be zero-extended to 64 bits before checking and
    // wrapped back to 32-bits after Spectre masking.  (And it's important that
    // the value we end up with has flowed through the Spectre mask.)
    //
    // If the memory's max size is known to be smaller than 64K pages exactly,
    // we can use a 32-bit check and avoid extension and wrapping.
    bool mem32LimitIs64Bits =
        isMem32(memoryIndex) &&
        !codeMeta().memories[memoryIndex].boundsCheckLimitIsAlways32Bits() &&
        MaxMemoryBytes(codeMeta().memories[memoryIndex].addressType(),
                       codeMeta().memories[memoryIndex].pageSize()) >=
            0x100000000;
#else
    // On 32-bit platforms we have no more than 2GB memory and the limit for a
    // 32-bit base pointer is never a 64-bit value.
    bool mem32LimitIs64Bits = false;
#endif
    return maybeLoadBoundsCheckLimit(memoryIndex,
                                     mem32LimitIs64Bits || isMem64(memoryIndex)
                                         ? MIRType::Int64
                                         : MIRType::Int32);
  }

  void performBoundsCheck(uint32_t memoryIndex, MDefinition** base,
                          MWasmLoadInstance* boundsCheckLimit) {
    // At the outset, actualBase could be the result of pretty much any integer
    // operation, or it could be the load of an integer constant.  If its type
    // is i32, we may assume the value has a canonical representation for the
    // platform, see doc block in MacroAssembler.h.
    MDefinition* actualBase = *base;

    // Extend an i32 index value to perform a 64-bit bounds check if the memory
    // can be 4GB or larger.
    bool extendAndWrapIndex =
        isMem32(memoryIndex) && boundsCheckLimit->type() == MIRType::Int64;
    if (extendAndWrapIndex) {
      auto* extended = MWasmExtendU32Index::New(alloc(), actualBase);
      curBlock_->add(extended);
      actualBase = extended;
    }

    auto* ins = MWasmBoundsCheck::New(alloc(), actualBase, boundsCheckLimit,
                                      trapSiteDesc(), MWasmBoundsCheck::Memory,
                                      memoryIndex);
    curBlock_->add(ins);
    actualBase = ins;

    // If we're masking, then we update *base to create a dependency chain
    // through the masked index.  But we will first need to wrap the index
    // value if it was extended above.
    if (JitOptions.spectreIndexMasking) {
      if (extendAndWrapIndex) {
        auto* wrapped = MWasmWrapU32Index::New(alloc(), actualBase);
        curBlock_->add(wrapped);
        actualBase = wrapped;
      }
      *base = actualBase;
    }
  }

  // Perform all necessary checking before a wasm heap access, based on the
  // attributes of the access and base pointer.
  //
  // For 64-bit indices on platforms that are limited to indices that fit into
  // 32 bits (all 32-bit platforms and mips64), this returns a bounds-checked
  // `base` that has type Int32.  Lowering code depends on this and will assert
  // that the base has this type.  See the end of this function.

  void checkOffsetAndAlignmentAndBounds(MemoryAccessDesc* access,
                                        MDefinition** base) {
    MOZ_ASSERT(!inDeadCode());

    // Attempt to fold a constant base pointer into the offset so as to simplify
    // the addressing expression. This may update *base.
    foldConstantPointer(access, base);

    // Determine whether an alignment check is needed and whether the offset
    // must be checked too.
    bool mustAddOffsetForAlignmentCheck = false;
    bool alignmentCheck =
        needAlignmentCheck(access, *base, &mustAddOffsetForAlignmentCheck);

    // If bounds checking or alignment checking requires it, compute the
    // effective address: add the offset into the pointer and trap on overflow.
    // This may update *base.
    maybeComputeEffectiveAddress(access, base, mustAddOffsetForAlignmentCheck);

    // Emit the alignment check if necessary; it traps if it fails.
    if (alignmentCheck) {
      curBlock_->add(MWasmAlignmentCheck::New(
          alloc(), *base, access->byteSize(), trapSiteDesc()));
    }

    // Emit the bounds check if necessary; it traps if it fails.  This may
    // update *base.
    MWasmLoadInstance* boundsCheckLimit =
        needBoundsCheck(access->memoryIndex());
    if (boundsCheckLimit) {
      performBoundsCheck(access->memoryIndex(), base, boundsCheckLimit);
    }

#ifndef JS_64BIT
    if (isMem64(access->memoryIndex())) {
      // We must have had an explicit bounds check (or one was elided if it was
      // proved redundant), and on 32-bit systems the index will for sure fit in
      // 32 bits: the max memory is 2GB.  So chop the index down to 32-bit to
      // simplify the back-end.
      MOZ_ASSERT((*base)->type() == MIRType::Int64);
      MOZ_ASSERT(!codeMeta().hugeMemoryEnabled(access->memoryIndex()));
      auto* chopped = MWasmWrapU32Index::New(alloc(), *base);
      MOZ_ASSERT(chopped->type() == MIRType::Int32);
      curBlock_->add(chopped);
      *base = chopped;
    }
#endif
  }

  bool isSmallerAccessForI64(ValType result, const MemoryAccessDesc* access) {
    if (result == ValType::I64 && access->byteSize() <= 4) {
      // These smaller accesses should all be zero-extending.
      MOZ_ASSERT(!isSignedIntType(access->type()));
      return true;
    }
    return false;
  }

 public:
  bool isMem32(uint32_t memoryIndex) {
    return codeMeta().memories[memoryIndex].addressType() == AddressType::I32;
  }
  bool isMem64(uint32_t memoryIndex) {
    return codeMeta().memories[memoryIndex].addressType() == AddressType::I64;
  }
  bool hugeMemoryEnabled(uint32_t memoryIndex) {
    return codeMeta().hugeMemoryEnabled(memoryIndex);
  }

  // Add the offset into the pointer to yield the EA; trap on overflow. Clears
  // the offset on the memory access as a result.
  MDefinition* computeEffectiveAddress(MDefinition* base,
                                       MemoryAccessDesc* access) {
    if (inDeadCode()) {
      return nullptr;
    }
    uint64_t offset = access->offset64();
    if (offset == 0) {
      return base;
    }
    auto* ins = MWasmAddOffset::New(alloc(), base, offset, trapSiteDesc());
    curBlock_->add(ins);
    access->clearOffset();
    return ins;
  }

  MDefinition* load(MDefinition* base, MemoryAccessDesc* access,
                    ValType result) {
    if (inDeadCode()) {
      return nullptr;
    }

    MDefinition* memoryBase = maybeLoadMemoryBase(access->memoryIndex());
    checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#endif
    MInstruction* load =
        MWasmLoad::New(alloc(), memoryBase, base, *access, result.toMIRType());
    if (!load) {
      return nullptr;
    }
    curBlock_->add(load);
    return load;
  }

  void store(MDefinition* base, MemoryAccessDesc* access, MDefinition* v) {
    if (inDeadCode()) {
      return;
    }

    MDefinition* memoryBase = maybeLoadMemoryBase(access->memoryIndex());
    checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#endif
    MInstruction* store =
        MWasmStore::New(alloc(), memoryBase, base, *access, v);
    if (!store) {
      return;
    }
    curBlock_->add(store);
  }

  MDefinition* atomicCompareExchangeHeap(MDefinition* base,
                                         MemoryAccessDesc* access,
                                         ValType result, MDefinition* oldv,
                                         MDefinition* newv) {
    if (inDeadCode()) {
      return nullptr;
    }

    checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#endif

    if (isSmallerAccessForI64(result, access)) {
      auto* cvtOldv =
          MWrapInt64ToInt32::New(alloc(), oldv, /*bottomHalf=*/true);
      curBlock_->add(cvtOldv);
      oldv = cvtOldv;

      auto* cvtNewv =
          MWrapInt64ToInt32::New(alloc(), newv, /*bottomHalf=*/true);
      curBlock_->add(cvtNewv);
      newv = cvtNewv;
    }

    MDefinition* memoryBase = maybeLoadMemoryBase(access->memoryIndex());
    MInstruction* cas = MWasmCompareExchangeHeap::New(
        alloc(), bytecodeOffset(), memoryBase, base, *access, oldv, newv,
        instancePointer_);
    if (!cas) {
      return nullptr;
    }
    curBlock_->add(cas);

    if (isSmallerAccessForI64(result, access)) {
      cas = MExtendInt32ToInt64::New(alloc(), cas, true);
      curBlock_->add(cas);
    }

    return cas;
  }

  MDefinition* atomicExchangeHeap(MDefinition* base, MemoryAccessDesc* access,
                                  ValType result, MDefinition* value) {
    if (inDeadCode()) {
      return nullptr;
    }

    checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#endif

    if (isSmallerAccessForI64(result, access)) {
      auto* cvtValue =
          MWrapInt64ToInt32::New(alloc(), value, /*bottomHalf=*/true);
      curBlock_->add(cvtValue);
      value = cvtValue;
    }

    MDefinition* memoryBase = maybeLoadMemoryBase(access->memoryIndex());
    MInstruction* xchg =
        MWasmAtomicExchangeHeap::New(alloc(), bytecodeOffset(), memoryBase,
                                     base, *access, value, instancePointer_);
    if (!xchg) {
      return nullptr;
    }
    curBlock_->add(xchg);

    if (isSmallerAccessForI64(result, access)) {
      xchg = MExtendInt32ToInt64::New(alloc(), xchg, true);
      curBlock_->add(xchg);
    }

    return xchg;
  }

  MDefinition* atomicBinopHeap(AtomicOp op, MDefinition* base,
                               MemoryAccessDesc* access, ValType result,
                               MDefinition* value) {
    if (inDeadCode()) {
      return nullptr;
    }

    checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#endif

    if (isSmallerAccessForI64(result, access)) {
      auto* cvtValue =
          MWrapInt64ToInt32::New(alloc(), value, /*bottomHalf=*/true);
      curBlock_->add(cvtValue);
      value = cvtValue;
    }

    MDefinition* memoryBase = maybeLoadMemoryBase(access->memoryIndex());
    MInstruction* binop =
        MWasmAtomicBinopHeap::New(alloc(), bytecodeOffset(), op, memoryBase,
                                  base, *access, value, instancePointer_);
    if (!binop) {
      return nullptr;
    }
    curBlock_->add(binop);

    if (isSmallerAccessForI64(result, access)) {
      binop = MExtendInt32ToInt64::New(alloc(), binop, true);
      curBlock_->add(binop);
    }

    return binop;
  }

#ifdef ENABLE_WASM_SIMD
  MDefinition* loadSplatSimd128(Scalar::Type viewType,
                                const LinearMemoryAddress<MDefinition*>& addr,
                                wasm::SimdOp splatOp) {
    if (inDeadCode()) {
      return nullptr;
    }

    MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                            trapSiteDesc(),
                            hugeMemoryEnabled(addr.memoryIndex));

    // Generate better code (on x86)
    // If AVX2 is enabled, more broadcast operators are available.
    if (viewType == Scalar::Float64
#  if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
        || (js::jit::CPUInfo::IsAVX2Present() &&
            (viewType == Scalar::Uint8 || viewType == Scalar::Uint16 ||
             viewType == Scalar::Float32))
#  endif
    ) {
      access.setSplatSimd128Load();
      return load(addr.base, &access, ValType::V128);
    }

    ValType resultType = ValType::I32;
    if (viewType == Scalar::Float32) {
      resultType = ValType::F32;
      splatOp = wasm::SimdOp::F32x4Splat;
    }
    auto* scalar = load(addr.base, &access, resultType);
    if (!inDeadCode() && !scalar) {
      return nullptr;
    }
    return scalarToSimd128(scalar, splatOp);
  }

  MDefinition* loadExtendSimd128(const LinearMemoryAddress<MDefinition*>& addr,
                                 wasm::SimdOp op) {
    if (inDeadCode()) {
      return nullptr;
    }

    // Generate better code (on x86) by loading as a double with an
    // operation that sign extends directly.
    MemoryAccessDesc access(addr.memoryIndex, Scalar::Float64, addr.align,
                            addr.offset, trapSiteDesc(),
                            hugeMemoryEnabled(addr.memoryIndex));
    access.setWidenSimd128Load(op);
    return load(addr.base, &access, ValType::V128);
  }

  MDefinition* loadZeroSimd128(Scalar::Type viewType, size_t numBytes,
                               const LinearMemoryAddress<MDefinition*>& addr) {
    if (inDeadCode()) {
      return nullptr;
    }

    MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                            trapSiteDesc(),
                            hugeMemoryEnabled(addr.memoryIndex));
    access.setZeroExtendSimd128Load();
    return load(addr.base, &access, ValType::V128);
  }

  MDefinition* loadLaneSimd128(uint32_t laneSize,
                               const LinearMemoryAddress<MDefinition*>& addr,
                               uint32_t laneIndex, MDefinition* src) {
    if (inDeadCode()) {
      return nullptr;
    }

    MemoryAccessDesc access(addr.memoryIndex, Scalar::Simd128, addr.align,
                            addr.offset, trapSiteDesc(),
                            hugeMemoryEnabled(addr.memoryIndex));
    MDefinition* memoryBase = maybeLoadMemoryBase(access.memoryIndex());
    MDefinition* base = addr.base;
    checkOffsetAndAlignmentAndBounds(&access, &base);
#  ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#  endif
    MInstruction* load = MWasmLoadLaneSimd128::New(
        alloc(), memoryBase, base, access, laneSize, laneIndex, src);
    if (!load) {
      return nullptr;
    }
    curBlock_->add(load);
    return load;
  }

  void storeLaneSimd128(uint32_t laneSize,
                        const LinearMemoryAddress<MDefinition*>& addr,
                        uint32_t laneIndex, MDefinition* src) {
    if (inDeadCode()) {
      return;
    }
    MemoryAccessDesc access(addr.memoryIndex, Scalar::Simd128, addr.align,
                            addr.offset, trapSiteDesc(),
                            hugeMemoryEnabled(addr.memoryIndex));
    MDefinition* memoryBase = maybeLoadMemoryBase(access.memoryIndex());
    MDefinition* base = addr.base;
    checkOffsetAndAlignmentAndBounds(&access, &base);
#  ifndef JS_64BIT
    MOZ_ASSERT(base->type() == MIRType::Int32);
#  endif
    MInstruction* store = MWasmStoreLaneSimd128::New(
        alloc(), memoryBase, base, access, laneSize, laneIndex, src);
    if (!store) {
      return;
    }
    curBlock_->add(store);
  }
#endif  // ENABLE_WASM_SIMD

  /************************************************ Global variable accesses */

  MDefinition* loadGlobalVar(const GlobalDesc& global) {
    if (inDeadCode()) {
      return nullptr;
    }

    MInstruction* load;
    if (global.isIndirect()) {
      // Pull a pointer to the value out of Instance::globalArea, then
      // load from that pointer.  Note that the pointer is immutable
      // even though the value it points at may change, hence the use of
      // |true| for the first node's |isConst| value, irrespective of
      // the |isConst| formal parameter to this method.  The latter
      // applies to the denoted value as a whole.
      auto* cellPtr = MWasmLoadInstanceDataField::New(
          alloc(), MIRType::Pointer, global.offset(),
          /*isConst=*/true, instancePointer_);
      curBlock_->add(cellPtr);
      load = MWasmLoadGlobalCell::New(alloc(), global.type().toMIRType(),
                                      cellPtr, global.type());
    } else {
      // Pull the value directly out of Instance::globalArea.
      load = MWasmLoadInstanceDataField::New(
          alloc(), global.type().toMIRType(), global.offset(),
          !global.isMutable(), instancePointer_,
          global.type().toMaybeRefType());
    }
    curBlock_->add(load);
    return load;
  }

  [[nodiscard]] bool storeGlobalVar(uint32_t bytecodeOffset,
                                    const GlobalDesc& global, MDefinition* v) {
    if (inDeadCode()) {
      return true;
    }

    if (global.isIndirect()) {
      // Pull a pointer to the value out of Instance::globalArea, then
      // store through that pointer.
      auto* valueAddr = MWasmLoadInstanceDataField::New(
          alloc(), MIRType::Pointer, global.offset(),
          /*isConstant=*/true, instancePointer_);
      curBlock_->add(valueAddr);

      // Handle a store to a ref-typed field specially
      if (global.type().toMIRType() == MIRType::WasmAnyRef) {
        // Load the previous value for the post-write barrier
        MOZ_ASSERT(v->type() == MIRType::WasmAnyRef);
        auto* prevValue = MWasmLoadGlobalCell::New(alloc(), MIRType::WasmAnyRef,
                                                   valueAddr, global.type());
        curBlock_->add(prevValue);

        // Store the new value
        auto* store =
            MWasmStoreRef::New(alloc(), instancePointer_, valueAddr,
                               /*valueOffset=*/0, v, AliasSet::WasmGlobalCell,
                               WasmPreBarrierKind::Normal);
        curBlock_->add(store);

        // Call the post-write barrier
        return postBarrierEdgePrecise(bytecodeOffset, valueAddr, prevValue);
      }

      auto* store = MWasmStoreGlobalCell::New(alloc(), v, valueAddr);
      curBlock_->add(store);
      return true;
    }
    // Or else store the value directly in Instance::globalArea.

    // Handle a store to a ref-typed field specially
    if (global.type().toMIRType() == MIRType::WasmAnyRef) {
      // Compute the address of the ref-typed global
      auto* valueAddr = MWasmDerivedPointer::New(
          alloc(), instancePointer_,
          wasm::Instance::offsetInData(global.offset()));
      curBlock_->add(valueAddr);

      // Load the previous value for the post-write barrier
      MOZ_ASSERT(v->type() == MIRType::WasmAnyRef);
      auto* prevValue = MWasmLoadGlobalCell::New(alloc(), MIRType::WasmAnyRef,
                                                 valueAddr, global.type());
      curBlock_->add(prevValue);

      // Store the new value
      auto* store =
          MWasmStoreRef::New(alloc(), instancePointer_, valueAddr,
                             /*valueOffset=*/0, v, AliasSet::WasmInstanceData,
                             WasmPreBarrierKind::Normal);
      curBlock_->add(store);

      // Call the post-write barrier
      return postBarrierEdgePrecise(bytecodeOffset, valueAddr, prevValue);
    }

    auto* store = MWasmStoreInstanceDataField::New(alloc(), global.offset(), v,
                                                   instancePointer_);
    curBlock_->add(store);
    return true;
  }

  // Load the slot on the instance where the result of `ref.func` is cached.
  // This may be null if a function reference for this function has not been
  // asked for yet.
  MDefinition* loadCachedRefFunc(uint32_t funcIndex) {
    uint32_t exportedFuncIndex = codeMeta().findFuncExportIndex(funcIndex);
    MWasmLoadInstanceDataField* refFunc = MWasmLoadInstanceDataField::New(
        alloc(), MIRType::WasmAnyRef,
        codeMeta().offsetOfFuncExportInstanceData(exportedFuncIndex) +
            offsetof(FuncExportInstanceData, func),
        true, instancePointer_);
    curBlock_->add(refFunc);
    return refFunc;
  }

  MDefinition* loadTableField(uint32_t tableIndex, unsigned fieldOffset,
                              MIRType type) {
    uint32_t instanceDataOffset = wasm::Instance::offsetInData(
        codeMeta().offsetOfTableInstanceData(tableIndex) + fieldOffset);
    auto* load =
        MWasmLoadInstance::New(alloc(), instancePointer_, instanceDataOffset,
                               type, AliasSet::Load(AliasSet::WasmTableMeta));
    curBlock_->add(load);
    return load;
  }

  MDefinition* loadTableLength(uint32_t tableIndex) {
    const TableDesc& table = codeMeta().tables[tableIndex];
    if (table.maximumLength().isSome() &&
        table.initialLength() == *table.maximumLength()) {
      return table.addressType() == AddressType::I64
                 ? constantI64(int64_t(table.initialLength()))
                 : constantI32(int32_t(table.initialLength()));
    }
    return loadTableField(tableIndex, offsetof(TableInstanceData, length),
                          table.addressType() == AddressType::I64
                              ? MIRType::Int64
                              : MIRType::Int32);
  }

  MDefinition* loadTableElements(uint32_t tableIndex) {
    return loadTableField(tableIndex, offsetof(TableInstanceData, elements),
                          MIRType::Pointer);
  }

  // Clamps a table address into i32 range. If the value is too large to fit in
  // an i32, it will be replaced with UINT32_MAX so that it will always fail a
  // 32-bit bounds check. Consider using an actual 64-bit bounds check if
  // possible.
  MDefinition* clampTableAddressToI32(AddressType addressType,
                                      MDefinition* address) {
    switch (addressType) {
      case AddressType::I32:
        return address;
      case AddressType::I64:
        auto* clamp = MWasmClampTable64Address::New(alloc(), address);
        if (!clamp) {
          return nullptr;
        }
        curBlock_->add(clamp);
        return clamp;
    }
    MOZ_CRASH("unknown address type");
  }

  MDefinition* boundsCheckAndWrapTableAddress(uint32_t tableIndex,
                                              MDefinition* address) {
    const TableDesc& table = codeMeta().tables[tableIndex];

    // Load the table length and perform a bounds check with spectre index
    // masking
    auto* length = loadTableLength(tableIndex);
    auto* check =
        MWasmBoundsCheck::New(alloc(), address, length, trapSiteDesc(),
                              MWasmBoundsCheck::Table, tableIndex);
    curBlock_->add(check);
    if (JitOptions.spectreIndexMasking) {
      address = check;
    }

    if (table.addressType() == AddressType::I64) {
      auto* address32 =
          MWrapInt64ToInt32::New(alloc(), address, /*bottomHalf=*/true);
      curBlock_->add(address32);
      return address32;
    }
    return address;
  };

  MDefinition* tableGetAnyRef(uint32_t tableIndex, MDefinition* address) {
    const TableDesc& table = codeMeta().tables[tableIndex];

    auto* address32 = boundsCheckAndWrapTableAddress(tableIndex, address);

    // Load the table elements and load the element
    auto* elements = loadTableElements(tableIndex);
    auto* element = MWasmLoadTableElement::New(alloc(), elements, address32,
                                               table.elemType());
    curBlock_->add(element);
    return element;
  }

  [[nodiscard]] bool tableSetAnyRef(uint32_t tableIndex, MDefinition* address,
                                    MDefinition* value,
                                    uint32_t bytecodeOffset) {
    const TableDesc& table = codeMeta().tables[tableIndex];

    auto* address32 = boundsCheckAndWrapTableAddress(tableIndex, address);

    // Load the table elements
    auto* elements = loadTableElements(tableIndex);

    // Load the previous value
    auto* prevValue = MWasmLoadTableElement::New(alloc(), elements, address32,
                                                 table.elemType());
    curBlock_->add(prevValue);

    // Compute the value's location for the post barrier
    auto* loc = MWasmDerivedIndexPointer::New(alloc(), elements, address32,
                                              ScalePointer);
    curBlock_->add(loc);

    // Store the new value
    auto* store = MWasmStoreRef::New(
        alloc(), instancePointer_, loc, /*valueOffset=*/0, value,
        AliasSet::WasmTableElement, WasmPreBarrierKind::Normal);
    curBlock_->add(store);

    // Perform the post barrier
    return postBarrierEdgePrecise(bytecodeOffset, loc, prevValue);
  }

  void addInterruptCheck() {
    if (inDeadCode()) {
      return;
    }
    curBlock_->add(
        MWasmInterruptCheck::New(alloc(), instancePointer_, trapSiteDesc()));
  }

  // Perform a post-write barrier to update the generational store buffer. This
  // version stores the entire containing object (e.g. a struct) rather than a
  // single edge.
  [[nodiscard]] bool postBarrierWholeCell(uint32_t bytecodeOffset,
                                          MDefinition* object,
                                          MDefinition* newValue) {
    auto* barrier = MWasmPostWriteBarrierWholeCell::New(
        alloc(), instancePointer_, object, newValue);
    if (!barrier) {
      return false;
    }
    curBlock_->add(barrier);
    return true;
  }

  // Perform a post-write barrier to update the generational store buffer. This
  // version tracks a single tenured -> nursery edge, and will remove a previous
  // store buffer entry if it is no longer needed.
  [[nodiscard]] bool postBarrierEdgePrecise(uint32_t bytecodeOffset,
                                            MDefinition* valueAddr,
                                            MDefinition* value) {
    return emitInstanceCall2(bytecodeOffset, SASigPostBarrierEdgePrecise,
                             valueAddr, value);
  }

  // Perform a post-write barrier to update the generational store buffer. This
  // version does not remove a previous store buffer entry if it is no longer
  // needed.
  [[nodiscard]] bool postBarrierEdgeAtIndex(uint32_t bytecodeOffset,
                                            MDefinition* object,
                                            MDefinition* valueBase,
                                            MDefinition* index, uint32_t scale,
                                            MDefinition* newValue) {
    auto* barrier = MWasmPostWriteBarrierEdgeAtIndex::New(
        alloc(), instancePointer_, object, valueBase, index, scale, newValue);
    if (!barrier) {
      return false;
    }
    curBlock_->add(barrier);
    return true;
  }

  /***************************************************************** Calls */

  // The IonMonkey backend maintains a single stack offset (from the stack
  // pointer to the base of the frame) by adding the total amount of spill
  // space required plus the maximum stack required for argument passing.
  // Since we do not use IonMonkey's MPrepareCall/MPassArg/MCall, we must
  // manually accumulate, for the entire function, the maximum required stack
  // space for argument passing. (This is passed to the CodeGenerator via
  // MIRGenerator::maxWasmStackArgBytes.) This is just be the maximum of the
  // stack space required for each individual call (as determined by the call
  // ABI).

  [[nodiscard]]
  bool passInstanceCallArg(MIRType instanceType, CallCompileState* callState) {
    if (inDeadCode()) {
      return true;
    }

    // Should only pass an instance once.  And it must be a non-GC pointer.
    MOZ_ASSERT(callState->instanceArg == ABIArg());
    MOZ_ASSERT(instanceType == MIRType::Pointer);
    callState->instanceArg = callState->abi.next(MIRType::Pointer);
    return true;
  }

  // Do not call this directly.  Call one of the passCallArg() variants instead.
  [[nodiscard]]
  bool passCallArgWorker(MDefinition* argDef, MIRType type,
                         CallCompileState* callState) {
    MOZ_ASSERT(argDef->type() == type);

    // Calling a softFP function requires moving our floats into GPRs.
    if (!callState->hardFP &&
        (type == MIRType::Double || type == MIRType::Float32)) {
      MIRType softType =
          (type == MIRType::Double) ? MIRType::Int64 : MIRType::Int32;
      auto* softDef = MReinterpretCast::New(alloc(), argDef, softType);
      if (!softDef) {
        return false;
      }
      curBlock_->add(softDef);
      argDef = softDef;
    }

    ABIArg arg = callState->abi.next(type);
    switch (arg.kind()) {
#ifdef JS_CODEGEN_REGISTER_PAIR
      case ABIArg::GPR_PAIR: {
        auto mirLow =
            MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ true);
        curBlock_->add(mirLow);
        auto mirHigh =
            MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ false);
        curBlock_->add(mirHigh);
        return callState->regArgs.append(
                   MWasmCallBase::Arg(AnyRegister(arg.gpr64().low), mirLow)) &&
               callState->regArgs.append(
                   MWasmCallBase::Arg(AnyRegister(arg.gpr64().high), mirHigh));
      }
#endif
      case ABIArg::GPR:
      case ABIArg::FPU:
        return callState->regArgs.append(MWasmCallBase::Arg(arg.reg(), argDef));
      case ABIArg::Stack: {
        auto* mir =
            MWasmStackArg::New(alloc(), arg.offsetFromArgBase(), argDef);
        curBlock_->add(mir);
        return true;
      }
      case ABIArg::Uninitialized:
        MOZ_ASSERT_UNREACHABLE("Uninitialized ABIArg kind");
    }
    MOZ_CRASH("Unknown ABIArg kind.");
  }

  template <typename VecT>
  [[nodiscard]]
  bool passCallArgs(const DefVector& argDefs, const VecT& types,
                    CallCompileState* callState) {
    MOZ_ASSERT(argDefs.length() == types.length());
    for (uint32_t i = 0; i < argDefs.length(); i++) {
      MDefinition* def = argDefs[i];
      ValType type = types[i];
      if (!passCallArg(def, type, callState)) {
        return false;
      }
    }
    return true;
  }

  [[nodiscard]]
  bool passCallArg(MDefinition* argDef, MIRType type,
                   CallCompileState* callState) {
    if (inDeadCode()) {
      return true;
    }
    return passCallArgWorker(argDef, type, callState);
  }

  [[nodiscard]]
  bool passCallArg(MDefinition* argDef, ValType type,
                   CallCompileState* callState) {
    if (inDeadCode()) {
      return true;
    }
    return passCallArgWorker(argDef, type.toMIRType(), callState);
  }

  // If the call returns results on the stack, prepare a stack area to receive
  // them, and pass the address of the stack area to the callee as an additional
  // argument.
  [[nodiscard]]
  bool passStackResultAreaCallArg(const ResultType& resultType,
                                  CallCompileState* callState) {
    if (inDeadCode()) {
      return true;
    }
    ABIResultIter iter(resultType);
    while (!iter.done() && iter.cur().inRegister()) {
      iter.next();
    }
    if (iter.done()) {
      // No stack results.
      return true;
    }

    // The builtin ABI only supports a single result value, so it doesn't
    // use stack results.
    MOZ_ASSERT(callState->abiKind == ABIKind::Wasm);

    auto* stackResultArea = MWasmStackResultArea::New(alloc());
    if (!stackResultArea) {
      return false;
    }
    if (!stackResultArea->init(alloc(), iter.remaining())) {
      return false;
    }
    for (uint32_t base = iter.index(); !iter.done(); iter.next()) {
      MWasmStackResultArea::StackResult loc(iter.cur().stackOffset(),
                                            iter.cur().type().toMIRType());
      stackResultArea->initResult(iter.index() - base, loc);
    }
    curBlock_->add(stackResultArea);
    MDefinition* def = callState->returnCall ? (MDefinition*)stackResultPointer_
                                             : (MDefinition*)stackResultArea;
    if (!passCallArg(def, MIRType::StackResults, callState)) {
      return false;
    }
    callState->stackResultArea = stackResultArea;
    return true;
  }

  [[nodiscard]]
  bool finishCallArgs(CallCompileState* callState) {
    if (inDeadCode()) {
      return true;
    }

    if (!callState->regArgs.append(
            MWasmCallBase::Arg(AnyRegister(InstanceReg), instancePointer_))) {
      return false;
    }

    uint32_t stackBytes = callState->abi.stackBytesConsumedSoFar();

    maxStackArgBytes_ = std::max(maxStackArgBytes_, stackBytes);
    return true;
  }

  [[nodiscard]]
  bool emitCallArgs(const FuncType& funcType, const DefVector& args,
                    CallCompileState* callState) {
    for (size_t i = 0, n = funcType.args().length(); i < n; ++i) {
      if (!mirGen().ensureBallast()) {
        return false;
      }
      if (!passCallArg(args[i], funcType.args()[i], callState)) {
        return false;
      }
    }

    ResultType resultType = ResultType::Vector(funcType.results());
    if (!passStackResultAreaCallArg(resultType, callState)) {
      return false;
    }

    return finishCallArgs(callState);
  }

  [[nodiscard]]
  bool collectBuiltinCallResult(MIRType type, MDefinition** result,
                                CallCompileState* callState) {
    MInstruction* def;
    switch (type) {
      case MIRType::Int32:
        def = MWasmRegisterResult::New(alloc(), MIRType::Int32, ReturnReg);
        break;
      case MIRType::Int64:
        def = MWasmRegister64Result::New(alloc(), ReturnReg64);
        break;
      case MIRType::Float32: {
        if (callState->abiKind == ABIKind::System) {
          def = MWasmSystemFloatRegisterResult::New(
              alloc(), type, ReturnFloat32Reg, callState->hardFP);
        } else {
          def = MWasmFloatRegisterResult::New(alloc(), MIRType::Float32,
                                              ReturnFloat32Reg);
        }
        break;
      }
      case MIRType::Double: {
        if (callState->abiKind == ABIKind::System) {
          def = MWasmSystemFloatRegisterResult::New(
              alloc(), type, ReturnDoubleReg, callState->hardFP);
        } else {
          def = MWasmFloatRegisterResult::New(alloc(), MIRType::Double,
                                              ReturnDoubleReg);
        }
        break;
      }
#ifdef ENABLE_WASM_SIMD
      case MIRType::Simd128:
        MOZ_CRASH("SIMD128 not supported in builtin ABI");
#endif
      case MIRType::WasmAnyRef:
        def = MWasmRegisterResult::New(alloc(), MIRType::WasmAnyRef, ReturnReg);
        break;
      case MIRType::None:
        MOZ_ASSERT(result == nullptr, "Not expecting any results created");
        return true;
      default:
        MOZ_CRASH("unexpected MIRType result for builtin call");
    }

    if (!def) {
      return false;
    }

    curBlock_->add(def);
    *result = def;

    return true;
  }

  [[nodiscard]]
  bool collectWasmCallResults(const ResultType& type,
                              CallCompileState* callState, DefVector* results) {
    // This function uses wasm::ABIResultIter which does not handle the system
    // ABI. Use collectBuiltinCallResult instead for builtin calls.
    MOZ_ASSERT(callState->abiKind == ABIKind::Wasm);
    MOZ_ASSERT(callState->hardFP);

    if (!results->reserve(type.length())) {
      return false;
    }

    // The result iterator goes in the order in which results would be popped
    // off; we want the order in which they would be pushed.
    ABIResultIter iter(type);
    uint32_t stackResultCount = 0;
    while (!iter.done()) {
      if (iter.cur().onStack()) {
        stackResultCount++;
      }
      iter.next();
    }

    for (iter.switchToPrev(); !iter.done(); iter.prev()) {
      if (!mirGen().ensureBallast()) {
        return false;
      }
      const ABIResult& result = iter.cur();
      MInstruction* def;
      if (result.inRegister()) {
        switch (result.type().kind()) {
          case wasm::ValType::I32:
            def =
                MWasmRegisterResult::New(alloc(), MIRType::Int32, result.gpr());
            break;
          case wasm::ValType::I64:
            def = MWasmRegister64Result::New(alloc(), result.gpr64());
            break;
          case wasm::ValType::F32:
            def = MWasmFloatRegisterResult::New(alloc(), MIRType::Float32,
                                                result.fpr());
            break;
          case wasm::ValType::F64:
            def = MWasmFloatRegisterResult::New(alloc(), MIRType::Double,
                                                result.fpr());
            break;
          case wasm::ValType::Ref:
            def = MWasmRegisterResult::New(alloc(), MIRType::WasmAnyRef,
                                           result.gpr(),
                                           result.type().toMaybeRefType());
            break;
          case wasm::ValType::V128:
#ifdef ENABLE_WASM_SIMD
            def = MWasmFloatRegisterResult::New(alloc(), MIRType::Simd128,
                                                result.fpr());
#else
            return this->iter().fail("Ion has no SIMD support yet");
#endif
        }
      } else {
        MOZ_ASSERT(callState->stackResultArea);
        MOZ_ASSERT(stackResultCount);
        uint32_t idx = --stackResultCount;
        def = MWasmStackResult::New(alloc(), callState->stackResultArea, idx);
      }

      if (!def) {
        return false;
      }
      curBlock_->add(def);
      results->infallibleAppend(def);
    }

    MOZ_ASSERT(results->length() == type.length());

    return true;
  }

  [[nodiscard]]
  bool call(CallCompileState* callState, const CallSiteDesc& desc,
            const CalleeDesc& callee, const ArgTypeVector& argTypes,
            MDefinition* addressOrRef = nullptr) {
    if (!beginCatchableCall(callState)) {
      return false;
    }

    MInstruction* ins;
    if (callState->isCatchable()) {
      ins = MWasmCallCatchable::New(
          alloc(), desc, callee, callState->regArgs,
          StackArgAreaSizeUnaligned(argTypes, callState->abiKind),
          callState->tryNoteIndex, callState->fallthroughBlock,
          callState->prePadBlock, addressOrRef);
    } else {
      ins = MWasmCallUncatchable::New(
          alloc(), desc, callee, callState->regArgs,
          StackArgAreaSizeUnaligned(argTypes, callState->abiKind),
          addressOrRef);
    }
    if (!ins) {
      return false;
    }
    curBlock_->add(ins);

    return finishCatchableCall(callState);
  }

  [[nodiscard]]
  CallRefHint auditInlineableCallees(InliningHeuristics::CallKind kind,
                                     CallRefHint hints) {
    // Takes candidates for inlining as provided in `hints`, and returns a
    // subset (or all) of them for which inlining is approved.  To indicate
    // that they are all disallowed, return an empty CallRefHint.

    MOZ_ASSERT_IF(kind == InliningHeuristics::CallKind::Direct,
                  hints.length() == 1);

    // We only support inlining when lazy tiering. This is currently a
    // requirement because we need the full module bytecode and function
    // definition ranges, which are not available in other modes.
    if (compilerEnv().mode() != CompileMode::LazyTiering) {
      return CallRefHint();
    }

    // If we were given no candidates, give up now.
    if (hints.empty()) {
      return CallRefHint();
    }

    // We can't inline if we've exceeded our per-root-function inlining
    // budget.
    //
    // This logic will cause `availableBudget` to be driven slightly negative
    // if a budget overshoot happens, so we will have performed slightly more
    // inlining than allowed by the initial setting of `availableBudget`.  The
    // size of this overshoot is however very limited -- it can't exceed the
    // size of three function bodies that are inlined (3 because that's what
    // CallRefHint can hold).  And the max size of an inlineable function body
    // is limited by InliningHeuristics::isSmallEnoughToInline.
    if (rootCompiler_.inliningBudget() < 0) {
      return CallRefHint();
    }

    // Check each candidate in turn, and add all acceptable ones to `filtered`.
    // It is important that `filtered` retains the same ordering as `hints`.
    CallRefHint filtered;
    for (uint32_t i = 0; i < hints.length(); i++) {
      uint32_t funcIndex = hints.get(i);

      // We can't inline an imported function.
      if (codeMeta().funcIsImport(funcIndex)) {
        continue;
      }

      // We do not support inlining a callee which uses tail calls
      FeatureUsage funcFeatureUsage =
          codeTailMeta()->funcDefFeatureUsage(funcIndex);
      if (funcFeatureUsage & FeatureUsage::ReturnCall) {
        continue;
      }

      // Ask the heuristics system if we're allowed to inline a function of
      // this size and kind at the current inlining depth.
      uint32_t inlineeBodySize = codeTailMeta()->funcDefRange(funcIndex).size();
      uint32_t rootFunctionBodySize = rootCompiler_.func().bytecodeSize();
      bool largeFunctionBackoff;
      bool smallEnough = InliningHeuristics::isSmallEnoughToInline(
          kind, inliningDepth(), inlineeBodySize, rootFunctionBodySize,
          &largeFunctionBackoff);
      if (largeFunctionBackoff) {
        rootCompiler_.noteLargeFunctionBackoffWasApplied();
      }
      if (!smallEnough) {
        continue;
      }

      filtered.append(funcIndex);
    }

    // Whatever ends up in `filtered` is approved for inlining.
    return filtered;
  }

  [[nodiscard]]
  bool finishInlinedCallDirect(FunctionCompiler& calleeCompiler,
                               DefVector* results) {
    const PendingInlineReturnVector& calleeReturns =
        calleeCompiler.pendingInlineReturns_;
    MBasicBlock* calleeCatchBlock = calleeCompiler.pendingInlineCatchBlock_;
    const FuncType& calleeFuncType = calleeCompiler.funcType();
    MBasicBlock* lastBlockBeforeCall = curBlock_;

    // Add the observed features from the inlined function to this function
    iter_.addFeatureUsage(calleeCompiler.featureUsage());

    // Create a block, if needed, to handle exceptions from the callee function
    if (calleeCatchBlock) {
      ControlInstructionVector* tryLandingPadPatches;
      bool inTryCode = inTryBlock(&tryLandingPadPatches);

      // The callee compiler should never create a catch block unless we have
      // a landing pad for it
      MOZ_RELEASE_ASSERT(inTryCode);

      // Create a block in our function to jump to the nearest try block. We
      // cannot just use the callee's catch block for this, as the slots on it
      // are set up for all the locals from that function. We need to create a
      // new block in our function with the slots for this function, that then
      // does the jump to the landing pad. Ion should be able to optimize this
      // away using jump threading.
      MBasicBlock* callerCatchBlock = nullptr;
      if (!newBlock(nullptr, &callerCatchBlock)) {
        return false;
      }

      // Our catch block inherits all of the locals state from immediately
      // before the inlined call
      callerCatchBlock->inheritSlots(lastBlockBeforeCall);

      // The callee catch block jumps to our catch block
      calleeCatchBlock->end(MGoto::New(alloc(), callerCatchBlock));

      // Our catch block has the callee rethrow block as a predecessor, but
      // ignores all phi's, because we use our own locals state.
      if (!callerCatchBlock->addPredecessorWithoutPhis(calleeCatchBlock)) {
        return false;
      }

      // Our catch block ends with a patch to jump to the enclosing try block.
      MBasicBlock* prevBlock = curBlock_;
      curBlock_ = callerCatchBlock;
      if (!endWithPadPatch(tryLandingPadPatches)) {
        return false;
      }
      curBlock_ = prevBlock;
    }

    // If there were no returns, then we are now in dead code
    if (calleeReturns.empty()) {
      curBlock_ = nullptr;
      return true;
    }

    // Create a block to join all of the returns from the inlined function
    MBasicBlock* joinAfterCall = nullptr;
    if (!newBlock(nullptr, &joinAfterCall)) {
      return false;
    }

    // The join block inherits all of the locals state from immediately before
    // the inlined call
    joinAfterCall->inheritSlots(lastBlockBeforeCall);

    // The join block has a phi node for every result of the inlined function
    // type. Each phi node has an operand for each of the returns of the
    // inlined function.
    for (uint32_t i = 0; i < calleeFuncType.results().length(); i++) {
      MPhi* phi = MPhi::New(alloc(), calleeFuncType.results()[i].toMIRType());
      if (!phi || !phi->reserveLength(calleeReturns.length())) {
        return false;
      }
      joinAfterCall->addPhi(phi);
      if (!results->append(phi)) {
        return false;
      }
    }

    // Bind every return from the inlined function to go to the join block, and
    // add the results for the return to the phi nodes.
    for (size_t i = 0; i < calleeReturns.length(); i++) {
      const PendingInlineReturn& calleeReturn = calleeReturns[i];

      // Setup the predecessor and successor relationship
      MBasicBlock* pred = calleeReturn.jump->block();
      if (!joinAfterCall->addPredecessorWithoutPhis(pred)) {
        return false;
      }
      calleeReturn.jump->replaceSuccessor(MGoto::TargetIndex, joinAfterCall);

      // For each result in this return, add it to the corresponding phi node
      for (uint32_t resultIndex = 0;
           resultIndex < calleeFuncType.results().length(); resultIndex++) {
        MDefinition* result = (*results)[resultIndex];
        ((MPhi*)(result))->addInput(calleeReturn.results[resultIndex]);
      }
    }

    // Continue MIR generation starting in the join block
    curBlock_ = joinAfterCall;

    return true;
  }

  [[nodiscard]]
  bool callDirect(const FuncType& funcType, uint32_t funcIndex,
                  uint32_t bytecodeOffset, const DefVector& args,
                  DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    CallCompileState callState(ABIKind::Wasm);
    CallSiteDesc desc(bytecodeOffset, rootCompiler_.inlinedCallerOffsetsIndex(),
                      CallSiteKind::Func);
    ResultType resultType = ResultType::Vector(funcType.results());
    auto callee = CalleeDesc::function(funcIndex);
    ArgTypeVector argTypes(funcType);

    return emitCallArgs(funcType, args, &callState) &&
           call(&callState, desc, callee, argTypes) &&
           collectWasmCallResults(resultType, &callState, results);
  }

  [[nodiscard]]
  bool returnCallDirect(const FuncType& funcType, uint32_t funcIndex,
                        uint32_t bytecodeOffset, const DefVector& args,
                        DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    // We do not support tail calls in inlined functions.
    MOZ_RELEASE_ASSERT(!isInlined());

    CallCompileState callState(ABIKind::Wasm);
    callState.returnCall = true;
    CallSiteDesc desc(bytecodeOffset, CallSiteKind::ReturnFunc);
    auto callee = CalleeDesc::function(funcIndex);
    ArgTypeVector argTypes(funcType);

    if (!emitCallArgs(funcType, args, &callState)) {
      return false;
    }

    auto ins = MWasmReturnCall::New(
        alloc(), desc, callee, callState.regArgs,
        StackArgAreaSizeUnaligned(argTypes, callState.abiKind), nullptr);
    if (!ins) {
      return false;
    }
    curBlock_->end(ins);
    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]]
  bool returnCallImport(unsigned globalDataOffset, uint32_t bytecodeOffset,
                        const FuncType& funcType, const DefVector& args,
                        DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    // We do not support tail calls in inlined functions.
    MOZ_RELEASE_ASSERT(!isInlined());

    CallCompileState callState(ABIKind::Wasm);
    callState.returnCall = true;
    CallSiteDesc desc(bytecodeOffset, CallSiteKind::Import);
    auto callee = CalleeDesc::import(globalDataOffset);
    ArgTypeVector argTypes(funcType);

    if (!emitCallArgs(funcType, args, &callState)) {
      return false;
    }

    auto* ins = MWasmReturnCall::New(
        alloc(), desc, callee, callState.regArgs,
        StackArgAreaSizeUnaligned(argTypes, callState.abiKind), nullptr);
    if (!ins) {
      return false;
    }
    curBlock_->end(ins);
    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]]
  bool returnCallIndirect(uint32_t funcTypeIndex, uint32_t tableIndex,
                          MDefinition* address, uint32_t bytecodeOffset,
                          const DefVector& args, DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    // We do not support tail calls in inlined functions.
    MOZ_RELEASE_ASSERT(!isInlined());

    const FuncType& funcType = (*codeMeta().types)[funcTypeIndex].funcType();
    CallIndirectId callIndirectId =
        CallIndirectId::forFuncType(codeMeta(), funcTypeIndex);

    CallCompileState callState(ABIKind::Wasm);
    callState.returnCall = true;
    CalleeDesc callee;
    const TableDesc& table = codeMeta().tables[tableIndex];
    callee =
        CalleeDesc::wasmTable(codeMeta(), table, tableIndex, callIndirectId);

    CallSiteDesc desc(bytecodeOffset, CallSiteKind::Indirect);
    ArgTypeVector argTypes(funcType);

    if (!emitCallArgs(funcType, args, &callState)) {
      return false;
    }

    auto* address32 = boundsCheckAndWrapTableAddress(tableIndex, address);
    if (!address32) {
      return false;
    }

    auto* ins = MWasmReturnCall::New(
        alloc(), desc, callee, callState.regArgs,
        StackArgAreaSizeUnaligned(argTypes, callState.abiKind), address32);
    if (!ins) {
      return false;
    }
    curBlock_->end(ins);
    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]]
  bool callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex,
                    MDefinition* address, uint32_t lineOrBytecode,
                    const DefVector& args, DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    CallCompileState callState(ABIKind::Wasm);
    const FuncType& funcType = (*codeMeta().types)[funcTypeIndex].funcType();
    CallIndirectId callIndirectId =
        CallIndirectId::forFuncType(codeMeta(), funcTypeIndex);

    CalleeDesc callee;
    const TableDesc& table = codeMeta().tables[tableIndex];
    callee =
        CalleeDesc::wasmTable(codeMeta(), table, tableIndex, callIndirectId);
    address = boundsCheckAndWrapTableAddress(tableIndex, address);
    if (!address) {
      return false;
    }

    CallSiteDesc desc(lineOrBytecode, rootCompiler_.inlinedCallerOffsetsIndex(),
                      CallSiteKind::Indirect);
    ArgTypeVector argTypes(funcType);
    ResultType resultType = ResultType::Vector(funcType.results());

    return emitCallArgs(funcType, args, &callState) &&
           call(&callState, desc, callee, argTypes, address) &&
           collectWasmCallResults(resultType, &callState, results);
  }

  [[nodiscard]]
  bool callImport(unsigned instanceDataOffset, uint32_t lineOrBytecode,
                  const FuncType& funcType, const DefVector& args,
                  DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    CallCompileState callState(ABIKind::Wasm);
    CallSiteDesc desc(lineOrBytecode, rootCompiler_.inlinedCallerOffsetsIndex(),
                      CallSiteKind::Import);
    auto callee = CalleeDesc::import(instanceDataOffset);
    ArgTypeVector argTypes(funcType);
    ResultType resultType = ResultType::Vector(funcType.results());

    return emitCallArgs(funcType, args, &callState) &&
           call(&callState, desc, callee, argTypes) &&
           collectWasmCallResults(resultType, &callState, results);
  }

  [[nodiscard]]
  bool builtinCall(CallCompileState* callState,
                   const SymbolicAddressSignature& builtin,
                   uint32_t bytecodeOffset, MDefinition** result) {
    if (inDeadCode()) {
      *result = nullptr;
      return true;
    }

    MOZ_ASSERT(builtin.failureMode == FailureMode::Infallible);

    CallSiteDesc desc(bytecodeOffset, rootCompiler_.inlinedCallerOffsetsIndex(),
                      CallSiteKind::Symbolic);
    auto callee = CalleeDesc::builtin(builtin.identity);

    auto* ins = MWasmCallUncatchable::New(
        alloc(), desc, callee, callState->regArgs,
        StackArgAreaSizeUnaligned(builtin, callState->abiKind));
    if (!ins) {
      return false;
    }
    curBlock_->add(ins);

    return collectBuiltinCallResult(builtin.retType, result, callState);
  }

  [[nodiscard]]
  bool builtinCall1(const SymbolicAddressSignature& builtin,
                    uint32_t bytecodeOffset, MDefinition* arg,
                    MDefinition** result) {
    CallCompileState callState(ABIForBuiltin(builtin.identity));
    return passCallArg(arg, builtin.argTypes[0], &callState) &&
           finishCallArgs(&callState) &&
           builtinCall(&callState, builtin, bytecodeOffset, result);
  }

  [[nodiscard]]
  bool builtinCall2(const SymbolicAddressSignature& builtin,
                    uint32_t bytecodeOffset, MDefinition* arg1,
                    MDefinition* arg2, MDefinition** result) {
    CallCompileState callState(ABIForBuiltin(builtin.identity));
    return passCallArg(arg1, builtin.argTypes[0], &callState) &&
           passCallArg(arg2, builtin.argTypes[1], &callState) &&
           finishCallArgs(&callState) &&
           builtinCall(&callState, builtin, bytecodeOffset, result);
  }

  [[nodiscard]]
  bool builtinCall5(const SymbolicAddressSignature& builtin,
                    uint32_t bytecodeOffset, MDefinition* arg1,
                    MDefinition* arg2, MDefinition* arg3, MDefinition* arg4,
                    MDefinition* arg5, MDefinition** result) {
    CallCompileState callState(ABIForBuiltin(builtin.identity));
    return passCallArg(arg1, builtin.argTypes[0], &callState) &&
           passCallArg(arg2, builtin.argTypes[1], &callState) &&
           passCallArg(arg3, builtin.argTypes[2], &callState) &&
           passCallArg(arg4, builtin.argTypes[3], &callState) &&
           passCallArg(arg5, builtin.argTypes[4], &callState) &&
           finishCallArgs(&callState) &&
           builtinCall(&callState, builtin, bytecodeOffset, result);
  }

  [[nodiscard]]
  bool builtinCall6(const SymbolicAddressSignature& builtin,
                    uint32_t bytecodeOffset, MDefinition* arg1,
                    MDefinition* arg2, MDefinition* arg3, MDefinition* arg4,
                    MDefinition* arg5, MDefinition* arg6,
                    MDefinition** result) {
    CallCompileState callState(ABIForBuiltin(builtin.identity));
    return passCallArg(arg1, builtin.argTypes[0], &callState) &&
           passCallArg(arg2, builtin.argTypes[1], &callState) &&
           passCallArg(arg3, builtin.argTypes[2], &callState) &&
           passCallArg(arg4, builtin.argTypes[3], &callState) &&
           passCallArg(arg5, builtin.argTypes[4], &callState) &&
           passCallArg(arg6, builtin.argTypes[5], &callState) &&
           finishCallArgs(&callState) &&
           builtinCall(&callState, builtin, bytecodeOffset, result);
  }

  [[nodiscard]]
  bool instanceCall(CallCompileState* callState,
                    const SymbolicAddressSignature& builtin,
                    uint32_t bytecodeOffset, MDefinition** result = nullptr) {
    MOZ_ASSERT_IF(!result, builtin.retType == MIRType::None);
    if (inDeadCode()) {
      if (result) {
        *result = nullptr;
      }
      return true;
    }

    CallSiteDesc desc(bytecodeOffset, rootCompiler_.inlinedCallerOffsetsIndex(),
                      CallSiteKind::Symbolic);
    if (builtin.failureMode != FailureMode::Infallible &&
        !beginCatchableCall(callState)) {
      return false;
    }

    MInstruction* ins;
    if (callState->isCatchable()) {
      ins = MWasmCallCatchable::NewBuiltinInstanceMethodCall(
          alloc(), desc, builtin.identity, builtin.failureMode,
          builtin.failureTrap, callState->instanceArg, callState->regArgs,
          StackArgAreaSizeUnaligned(builtin, callState->abiKind),
          callState->tryNoteIndex, callState->fallthroughBlock,
          callState->prePadBlock);
    } else {
      ins = MWasmCallUncatchable::NewBuiltinInstanceMethodCall(
          alloc(), desc, builtin.identity, builtin.failureMode,
          builtin.failureTrap, callState->instanceArg, callState->regArgs,
          StackArgAreaSizeUnaligned(builtin, callState->abiKind));
    }
    if (!ins) {
      return false;
    }
    curBlock_->add(ins);

    if (!finishCatchableCall(callState)) {
      return false;
    }

    if (!result) {
      return true;
    }
    return collectBuiltinCallResult(builtin.retType, result, callState);
  }

  /*********************************************** Instance call helpers ***/

  // Do not call this function directly -- it offers no protection against
  // mis-counting of arguments.  Instead call one of
  // ::emitInstanceCall{0,1,2,3,4,5,6}.
  //
  // Emits a call to the Instance function indicated by `callee`.  This is
  // assumed to take an Instance pointer as its first argument.  The remaining
  // args are taken from `args`, which is assumed to hold `numArgs` entries.
  // If `result` is non-null, the MDefinition* holding the return value is
  // written to `*result`.
  [[nodiscard]]
  bool emitInstanceCallN(uint32_t bytecodeOffset,
                         const SymbolicAddressSignature& callee,
                         MDefinition** args, size_t numArgs,
                         MDefinition** result = nullptr) {
    // Check that the first formal parameter is plausibly an Instance pointer.
    MOZ_ASSERT(callee.numArgs > 0);
    MOZ_ASSERT(callee.argTypes[0] == MIRType::Pointer);
    // Check we agree on the number of args.
    MOZ_ASSERT(numArgs + 1 /* the instance pointer */ == callee.numArgs);
    // Check we agree on whether a value is returned.
    MOZ_ASSERT((result == nullptr) == (callee.retType == MIRType::None));

    // If we are in dead code, it can happen that some of the `args` entries
    // are nullptr, which will look like an OOM to the logic below.  So exit
    // at this point.  `passInstanceCallArg`, `passCallArg`, `finishCall` and
    // `instanceCall` all do nothing in dead code, so it's valid
    // to exit here.
    if (inDeadCode()) {
      if (result) {
        *result = nullptr;
      }
      return true;
    }

    // Check all args for signs of OOMness before attempting to allocating any
    // more memory.
    for (size_t i = 0; i < numArgs; i++) {
      if (!args[i]) {
        if (result) {
          *result = nullptr;
        }
        return false;
      }
    }

    // Finally, construct the call.
    CallCompileState callState(ABIForBuiltin(callee.identity));
    if (!passInstanceCallArg(callee.argTypes[0], &callState)) {
      return false;
    }
    for (size_t i = 0; i < numArgs; i++) {
      if (!passCallArg(args[i], callee.argTypes[i + 1], &callState)) {
        return false;
      }
    }
    if (!finishCallArgs(&callState)) {
      return false;
    }
    return instanceCall(&callState, callee, bytecodeOffset, result);
  }

  [[nodiscard]]
  bool emitInstanceCall0(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition** result = nullptr) {
    MDefinition* args[0] = {};
    return emitInstanceCallN(lineOrBytecode, callee, args, 0, result);
  }
  [[nodiscard]]
  bool emitInstanceCall1(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition* arg1, MDefinition** result = nullptr) {
    MDefinition* args[1] = {arg1};
    return emitInstanceCallN(lineOrBytecode, callee, args, 1, result);
  }
  [[nodiscard]]
  bool emitInstanceCall2(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition* arg1, MDefinition* arg2,
                         MDefinition** result = nullptr) {
    MDefinition* args[2] = {arg1, arg2};
    return emitInstanceCallN(lineOrBytecode, callee, args, 2, result);
  }
  [[nodiscard]]
  bool emitInstanceCall3(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition* arg1, MDefinition* arg2,
                         MDefinition* arg3, MDefinition** result = nullptr) {
    MDefinition* args[3] = {arg1, arg2, arg3};
    return emitInstanceCallN(lineOrBytecode, callee, args, 3, result);
  }
  [[nodiscard]]
  bool emitInstanceCall4(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition* arg1, MDefinition* arg2,
                         MDefinition* arg3, MDefinition* arg4,
                         MDefinition** result = nullptr) {
    MDefinition* args[4] = {arg1, arg2, arg3, arg4};
    return emitInstanceCallN(lineOrBytecode, callee, args, 4, result);
  }
  [[nodiscard]]
  bool emitInstanceCall5(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition* arg1, MDefinition* arg2,
                         MDefinition* arg3, MDefinition* arg4,
                         MDefinition* arg5, MDefinition** result = nullptr) {
    MDefinition* args[5] = {arg1, arg2, arg3, arg4, arg5};
    return emitInstanceCallN(lineOrBytecode, callee, args, 5, result);
  }
  [[nodiscard]]
  bool emitInstanceCall6(uint32_t lineOrBytecode,
                         const SymbolicAddressSignature& callee,
                         MDefinition* arg1, MDefinition* arg2,
                         MDefinition* arg3, MDefinition* arg4,
                         MDefinition* arg5, MDefinition* arg6,
                         MDefinition** result = nullptr) {
    MDefinition* args[6] = {arg1, arg2, arg3, arg4, arg5, arg6};
    return emitInstanceCallN(lineOrBytecode, callee, args, 6, result);
  }

  [[nodiscard]]
  bool callRef(const FuncType& funcType, MDefinition* ref,
               uint32_t lineOrBytecode, const DefVector& args,
               DefVector* results) {
    MOZ_ASSERT(!inDeadCode());

    CallCompileState callState(ABIKind::Wasm);
    CalleeDesc callee = CalleeDesc::wasmFuncRef();
    CallSiteDesc desc(lineOrBytecode, rootCompiler_.inlinedCallerOffsetsIndex(),
                      CallSiteKind::FuncRef);
    ArgTypeVector argTypes(funcType);
    ResultType resultType = ResultType::Vector(funcType.results());

    return emitCallArgs(funcType, args, &callState) &&
           call(&callState, desc, callee, argTypes, ref) &&
           collectWasmCallResults(resultType, &callState, results);
  }

  [[nodiscard]]
  bool returnCallRef(const FuncType& funcType, MDefinition* ref,
                     uint32_t bytecodeOffset, const DefVector& args,
                     DefVector* results) {
    MOZ_ASSERT(!inDeadCode());
    MOZ_ASSERT(!isInlined());

    CallCompileState callState(ABIKind::Wasm);
    callState.returnCall = true;
    CalleeDesc callee = CalleeDesc::wasmFuncRef();
    CallSiteDesc desc(bytecodeOffset, CallSiteKind::FuncRef);
    ArgTypeVector argTypes(funcType);

    if (!emitCallArgs(funcType, args, &callState)) {
      return false;
    }

    auto* ins = MWasmReturnCall::New(
        alloc(), desc, callee, callState.regArgs,
        StackArgAreaSizeUnaligned(argTypes, callState.abiKind), ref);
    if (!ins) {
      return false;
    }
    curBlock_->end(ins);
    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]] MDefinition* stringCast(MDefinition* string) {
    auto* ins = MWasmTrapIfAnyRefIsNotJSString::New(
        alloc(), string, wasm::Trap::BadCast, trapSiteDesc());
    if (!ins) {
      return ins;
    }
    curBlock_->add(ins);
    return ins;
  }

  [[nodiscard]] MDefinition* stringTest(MDefinition* string) {
    auto* ins = MWasmAnyRefIsJSString::New(alloc(), string);
    if (!ins) {
      return nullptr;
    }
    curBlock_->add(ins);
    return ins;
  }

  [[nodiscard]] MDefinition* stringLength(MDefinition* string) {
    auto* ins = MWasmAnyRefJSStringLength::New(
        alloc(), string, wasm::Trap::BadCast, trapSiteDesc());
    if (!ins) {
      return nullptr;
    }
    curBlock_->add(ins);
    return ins;
  }

  [[nodiscard]] bool dispatchInlineBuiltinModuleFunc(
      const BuiltinModuleFunc& builtinModuleFunc, const DefVector& params) {
    BuiltinInlineOp inlineOp = builtinModuleFunc.inlineOp();
    MOZ_ASSERT(inlineOp != BuiltinInlineOp::None);
    switch (inlineOp) {
      case BuiltinInlineOp::StringCast: {
        MOZ_ASSERT(params.length() == 1);
        MDefinition* string = params[0];
        MDefinition* cast = stringCast(string);
        if (!cast) {
          return false;
        }
        iter().setResult(string);
        return true;
      }
      case BuiltinInlineOp::StringTest: {
        MOZ_ASSERT(params.length() == 1);
        MDefinition* string = params[0];
        MDefinition* test = stringTest(string);
        if (!test) {
          return false;
        }
        iter().setResult(test);
        return true;
      }
      case BuiltinInlineOp::StringLength: {
        MOZ_ASSERT(params.length() == 1);
        MDefinition* string = params[0];
        MDefinition* length = stringLength(string);
        if (!length) {
          return false;
        }
        iter().setResult(length);
        return true;
      }
      case BuiltinInlineOp::None:
      case BuiltinInlineOp::Limit:
        break;
    }
    MOZ_CRASH();
  }

  [[nodiscard]] bool callBuiltinModuleFunc(
      const BuiltinModuleFunc& builtinModuleFunc, const DefVector& params) {
    MOZ_ASSERT(!inDeadCode());

    BuiltinInlineOp inlineOp = builtinModuleFunc.inlineOp();
    if (inlineOp != BuiltinInlineOp::None) {
      return dispatchInlineBuiltinModuleFunc(builtinModuleFunc, params);
    }

    // It's almost possible to use FunctionCompiler::emitInstanceCallN here.
    // Unfortunately not currently possible though, since ::emitInstanceCallN
    // expects an array of arguments along with a size, and that's not what is
    // available here.  It would be possible if we were prepared to copy
    // `builtinModuleFunc->params` into a fixed-sized (16 element?) array, add
    // `memoryBase`, and make the call.
    const SymbolicAddressSignature& callee = *builtinModuleFunc.sig();

    CallCompileState callState(ABIForBuiltin(callee.identity));
    if (!passInstanceCallArg(callee.argTypes[0], &callState) ||
        !passCallArgs(params, builtinModuleFunc.funcType()->args(),
                      &callState)) {
      return false;
    }

    if (builtinModuleFunc.usesMemory()) {
      if (!passCallArg(memoryBase(0), MIRType::Pointer, &callState)) {
        return false;
      }
    }

    if (!finishCallArgs(&callState)) {
      return false;
    }

    bool hasResult = !builtinModuleFunc.funcType()->results().empty();
    MDefinition* result = nullptr;
    MDefinition** resultOutParam = hasResult ? &result : nullptr;
    if (!instanceCall(&callState, callee, readBytecodeOffset(),
                      resultOutParam)) {
      return false;
    }

    if (hasResult) {
      iter().setResult(result);
    }
    return true;
  }

  /*********************************************** Control flow generation */

  inline bool inDeadCode() const { return curBlock_ == nullptr; }

  [[nodiscard]] bool returnValues(DefVector&& values) {
    if (inDeadCode()) {
      return true;
    }

    // If we're inlined into another function, we must accumulate the returns
    // so that they can be patched into the caller function.
    if (isInlined()) {
      MGoto* jump = MGoto::New(alloc());
      if (!jump) {
        return false;
      }
      curBlock_->end(jump);
      curBlock_ = nullptr;
      return pendingInlineReturns_.emplaceBack(
          PendingInlineReturn(jump, std::move(values)));
    }

    if (values.empty()) {
      curBlock_->end(MWasmReturnVoid::New(alloc(), instancePointer_));
    } else {
      ResultType resultType = ResultType::Vector(funcType().results());
      ABIResultIter iter(resultType);
      // Switch to iterate in FIFO order instead of the default LIFO.
      while (!iter.done()) {
        iter.next();
      }
      iter.switchToPrev();
      for (uint32_t i = 0; !iter.done(); iter.prev(), i++) {
        if (!mirGen().ensureBallast()) {
          return false;
        }
        const ABIResult& result = iter.cur();
        if (result.onStack()) {
          MOZ_ASSERT(iter.remaining() > 1);
          auto* store = MWasmStoreStackResult::New(
              alloc(), stackResultPointer_, result.stackOffset(), values[i]);
          curBlock_->add(store);
        } else {
          MOZ_ASSERT(iter.remaining() == 1);
          MOZ_ASSERT(i + 1 == values.length());
          curBlock_->end(
              MWasmReturn::New(alloc(), values[i], instancePointer_));
        }
      }
    }
    curBlock_ = nullptr;
    return true;
  }

  void unreachableTrap() {
    if (inDeadCode()) {
      return;
    }

    auto* ins =
        MWasmTrap::New(alloc(), wasm::Trap::Unreachable, trapSiteDesc());
    curBlock_->end(ins);
    curBlock_ = nullptr;
  }

  void unimplementedTrap() {
    if (inDeadCode()) {
      return;
    }

    auto* ins =
        MWasmTrap::New(alloc(), wasm::Trap::Unimplemented, trapSiteDesc());
    curBlock_->end(ins);
    curBlock_ = nullptr;
  }

 private:
  static uint32_t numPushed(MBasicBlock* block) {
    return block->stackDepth() - block->info().firstStackSlot();
  }

 public:
  [[nodiscard]] bool pushDefs(const DefVector& defs) {
    if (inDeadCode()) {
      return true;
    }
    MOZ_ASSERT(numPushed(curBlock_) == 0);
    if (!curBlock_->ensureHasSlots(defs.length())) {
      return false;
    }
    for (MDefinition* def : defs) {
      MOZ_ASSERT(def->type() != MIRType::None);
      curBlock_->push(def);
    }
    return true;
  }

  [[nodiscard]] bool popPushedDefs(DefVector* defs) {
    size_t n = numPushed(curBlock_);
    if (!defs->resizeUninitialized(n)) {
      return false;
    }
    for (; n > 0; n--) {
      MDefinition* def = curBlock_->pop();
      MOZ_ASSERT(def->type() != MIRType::Value);
      (*defs)[n - 1] = def;
    }
    return true;
  }

 private:
  [[nodiscard]] bool addJoinPredecessor(const DefVector& defs,
                                        MBasicBlock** joinPred) {
    *joinPred = curBlock_;
    if (inDeadCode()) {
      return true;
    }
    return pushDefs(defs);
  }

 public:
  [[nodiscard]] bool branchAndStartThen(MDefinition* cond,
                                        MBasicBlock** elseBlock) {
    if (inDeadCode()) {
      *elseBlock = nullptr;
    } else {
      MBasicBlock* thenBlock;
      if (!newBlock(curBlock_, &thenBlock)) {
        return false;
      }
      if (!newBlock(curBlock_, elseBlock)) {
        return false;
      }

      curBlock_->end(MTest::New(alloc(), cond, thenBlock, *elseBlock));

      curBlock_ = thenBlock;
      mirGraph().moveBlockToEnd(curBlock_);
    }

    return startBlock();
  }

  [[nodiscard]] bool switchToElse(MBasicBlock* elseBlock,
                                  MBasicBlock** thenJoinPred) {
    DefVector values;
    if (!finishBlock(&values)) {
      return false;
    }

    if (!elseBlock) {
      *thenJoinPred = nullptr;
    } else {
      if (!addJoinPredecessor(values, thenJoinPred)) {
        return false;
      }

      curBlock_ = elseBlock;
      mirGraph().moveBlockToEnd(curBlock_);
    }

    return startBlock();
  }

  [[nodiscard]] bool joinIfElse(MBasicBlock* thenJoinPred, DefVector* defs) {
    DefVector values;
    if (!finishBlock(&values)) {
      return false;
    }

    if (!thenJoinPred && inDeadCode()) {
      return true;
    }

    MBasicBlock* elseJoinPred;
    if (!addJoinPredecessor(values, &elseJoinPred)) {
      return false;
    }

    mozilla::Array<MBasicBlock*, 2> blocks;
    size_t numJoinPreds = 0;
    if (thenJoinPred) {
      blocks[numJoinPreds++] = thenJoinPred;
    }
    if (elseJoinPred) {
      blocks[numJoinPreds++] = elseJoinPred;
    }

    if (numJoinPreds == 0) {
      return true;
    }

    MBasicBlock* join;
    if (!goToNewBlock(blocks[0], &join)) {
      return false;
    }
    for (size_t i = 1; i < numJoinPreds; ++i) {
      if (!goToExistingBlock(blocks[i], join)) {
        return false;
      }
    }

    curBlock_ = join;
    return popPushedDefs(defs);
  }

  [[nodiscard]] bool startBlock() {
    MOZ_ASSERT_IF(pendingBlockDepth_ < pendingBlocks_.length(),
                  pendingBlocks_[pendingBlockDepth_].patches.empty());
    pendingBlockDepth_++;
    return true;
  }

  [[nodiscard]] bool finishBlock(DefVector* defs) {
    MOZ_ASSERT(pendingBlockDepth_);
    uint32_t topLabel = --pendingBlockDepth_;
    return bindBranches(topLabel, defs);
  }

  [[nodiscard]] bool startLoop(MBasicBlock** loopHeader, size_t paramCount) {
    *loopHeader = nullptr;

    pendingBlockDepth_++;
    rootCompiler_.startLoop();

    if (inDeadCode()) {
      return true;
    }

    // Create the loop header.
    MOZ_ASSERT(curBlock_->loopDepth() == rootCompiler_.loopDepth() - 1);
    *loopHeader = MBasicBlock::New(mirGraph(), info(), curBlock_,
                                   MBasicBlock::PENDING_LOOP_HEADER);
    if (!*loopHeader) {
      return false;
    }

    (*loopHeader)->setLoopDepth(rootCompiler_.loopDepth());
    mirGraph().addBlock(*loopHeader);
    curBlock_->end(MGoto::New(alloc(), *loopHeader));

    DefVector loopParams;
    if (!iter().getResults(paramCount, &loopParams)) {
      return false;
    }

    // Eagerly create a phi for all loop params. setLoopBackedge will remove
    // any that were not necessary.
    for (size_t i = 0; i < paramCount; i++) {
      MPhi* phi = MPhi::New(alloc(), loopParams[i]->type());
      if (!phi) {
        return false;
      }
      if (!phi->reserveLength(2)) {
        return false;
      }
      (*loopHeader)->addPhi(phi);
      phi->addInput(loopParams[i]);
      loopParams[i] = phi;
    }
    iter().setResults(paramCount, loopParams);

    MBasicBlock* body;
    if (!goToNewBlock(*loopHeader, &body)) {
      return false;
    }
    curBlock_ = body;
    return true;
  }

 private:
  void fixupRedundantPhis(MBasicBlock* b) {
    for (size_t i = 0, depth = b->stackDepth(); i < depth; i++) {
      MDefinition* def = b->getSlot(i);
      if (def->isUnused()) {
        b->setSlot(i, def->toPhi()->getOperand(0));
      }
    }
  }

  [[nodiscard]] bool setLoopBackedge(MBasicBlock* loopEntry,
                                     MBasicBlock* loopBody,
                                     MBasicBlock* backedge, size_t paramCount) {
    if (!loopEntry->setBackedgeWasm(backedge, paramCount)) {
      return false;
    }

    // Entering a loop will eagerly create a phi node for all locals and loop
    // params. Now that we've closed the loop we can check which phi nodes
    // were actually needed by checking if the SSA definition flowing into the
    // loop header (operand 0) is different than the SSA definition coming from
    // the loop backedge (operand 1). If they are the same definition, the phi
    // is redundant and can be removed.
    //
    // To do this we mark all redundant phis as 'unused', then remove the phi's
    // from places in ourself the phis may have flowed into, then replace all
    // uses of the phi's in the MIR graph with the original SSA definition.
    for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd();
         phi++) {
      MOZ_ASSERT(phi->numOperands() == 2);
      if (phi->getOperand(0) == phi->getOperand(1)) {
        phi->setUnused();
      }
    }

    // Fix up phis stored in the slots Vector of pending blocks.
    for (PendingBlockTarget& pendingBlockTarget : pendingBlocks_) {
      for (ControlFlowPatch& p : pendingBlockTarget.patches) {
        MBasicBlock* block = p.ins->block();
        if (block->loopDepth() >= loopEntry->loopDepth()) {
          fixupRedundantPhis(block);
        }
      }
    }

    // The loop body, if any, might be referencing recycled phis too.
    if (loopBody) {
      fixupRedundantPhis(loopBody);
    }

    // Pending jumps to an enclosing try-catch may reference the recycled phis.
    // We have to search above all enclosing try blocks, as a delegate may move
    // patches around.
    for (uint32_t depth = 0; depth < iter().controlStackDepth(); depth++) {
      LabelKind kind = iter().controlKind(depth);
      if (kind != LabelKind::Try && kind != LabelKind::TryTable &&
          kind != LabelKind::Body) {
        continue;
      }
      Control& control = iter().controlItem(depth);
      if (!control.tryControl) {
        continue;
      }
      for (MControlInstruction* patch : control.tryControl->landingPadPatches) {
        MBasicBlock* block = patch->block();
        if (block->loopDepth() >= loopEntry->loopDepth()) {
          fixupRedundantPhis(block);
        }
      }
    }
    for (MControlInstruction* patch : bodyRethrowPadPatches_) {
      MBasicBlock* block = patch->block();
      if (block->loopDepth() >= loopEntry->loopDepth()) {
        fixupRedundantPhis(block);
      }
    }

    // If we're inlined into another function we are accumulating return values
    // in a vector, search through the results to see if any refer to a
    // redundant phi.
    for (PendingInlineReturn& pendingReturn : pendingInlineReturns_) {
      for (uint32_t resultIndex = 0;
           resultIndex < pendingReturn.results.length(); resultIndex++) {
        MDefinition** pendingResult = &pendingReturn.results[resultIndex];
        if ((*pendingResult)->isUnused()) {
          *pendingResult = (*pendingResult)->toPhi()->getOperand(0);
        }
      }
    }

    // Discard redundant phis and add to the free list.
    for (MPhiIterator phi = loopEntry->phisBegin();
         phi != loopEntry->phisEnd();) {
      MPhi* entryDef = *phi++;
      if (!entryDef->isUnused()) {
        continue;
      }

      entryDef->justReplaceAllUsesWith(entryDef->getOperand(0));
      loopEntry->discardPhi(entryDef);
      mirGraph().addPhiToFreeList(entryDef);
    }

    return true;
  }

 public:
  [[nodiscard]] bool closeLoop(MBasicBlock* loopHeader,
                               DefVector* loopResults) {
    MOZ_ASSERT(pendingBlockDepth_ >= 1);
    MOZ_ASSERT(rootCompiler_.loopDepth());

    uint32_t headerLabel = pendingBlockDepth_ - 1;

    if (!loopHeader) {
      MOZ_ASSERT(inDeadCode());
      MOZ_ASSERT(headerLabel >= pendingBlocks_.length() ||
                 pendingBlocks_[headerLabel].patches.empty());
      pendingBlockDepth_--;
      rootCompiler_.closeLoop();
      return true;
    }

    // Op::Loop doesn't have an implicit backedge so temporarily set
    // aside the end of the loop body to bind backedges.
    MBasicBlock* loopBody = curBlock_;
    curBlock_ = nullptr;

    // As explained in bug 1253544, Ion apparently has an invariant that
    // there is only one backedge to loop headers. To handle wasm's ability
    // to have multiple backedges to the same loop header, we bind all those
    // branches as forward jumps to a single backward jump. This is
    // unfortunate but the optimizer is able to fold these into single jumps
    // to backedges.
    DefVector backedgeValues;
    if (!bindBranches(headerLabel, &backedgeValues)) {
      return false;
    }

    MOZ_ASSERT(loopHeader->loopDepth() == rootCompiler_.loopDepth());

    if (curBlock_) {
      // We're on the loop backedge block, created by bindBranches.
      for (size_t i = 0, n = numPushed(curBlock_); i != n; i++) {
        curBlock_->pop();
      }

      if (!pushDefs(backedgeValues)) {
        return false;
      }

      MOZ_ASSERT(curBlock_->loopDepth() == rootCompiler_.loopDepth());
      curBlock_->end(MGoto::New(alloc(), loopHeader));
      if (!setLoopBackedge(loopHeader, loopBody, curBlock_,
                           backedgeValues.length())) {
        return false;
      }
    }

    curBlock_ = loopBody;

    rootCompiler_.closeLoop();

    // If the loop depth still at the inner loop body, correct it.
    if (curBlock_ && curBlock_->loopDepth() != rootCompiler_.loopDepth()) {
      MBasicBlock* out;
      if (!goToNewBlock(curBlock_, &out)) {
        return false;
      }
      curBlock_ = out;
    }

    pendingBlockDepth_ -= 1;
    return inDeadCode() || popPushedDefs(loopResults);
  }

  [[nodiscard]] bool addControlFlowPatch(
      MControlInstruction* ins, uint32_t relative, uint32_t index,
      BranchHint branchHint = BranchHint::Invalid) {
    MOZ_ASSERT(relative < pendingBlockDepth_);
    uint32_t absolute = pendingBlockDepth_ - 1 - relative;

    if (absolute >= pendingBlocks_.length() &&
        !pendingBlocks_.resize(absolute + 1)) {
      return false;
    }

    pendingBlocks_[absolute].hint = branchHint;
    return pendingBlocks_[absolute].patches.append(
        ControlFlowPatch(ins, index));
  }

  [[nodiscard]] bool br(uint32_t relativeDepth, const DefVector& values) {
    if (inDeadCode()) {
      return true;
    }

    MGoto* jump = MGoto::New(alloc());
    if (!addControlFlowPatch(jump, relativeDepth, MGoto::TargetIndex)) {
      return false;
    }

    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(jump);
    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]] bool brIf(uint32_t relativeDepth, const DefVector& values,
                          MDefinition* condition, BranchHint branchHint) {
    if (inDeadCode()) {
      return true;
    }

    MBasicBlock* joinBlock = nullptr;
    if (!newBlock(curBlock_, &joinBlock)) {
      return false;
    }

    MTest* test = MTest::New(alloc(), condition, nullptr, joinBlock);
    if (!addControlFlowPatch(test, relativeDepth, MTest::TrueBranchIndex,
                             branchHint)) {
      return false;
    }

    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(test);
    curBlock_ = joinBlock;

    return true;
  }

  [[nodiscard]] bool brTable(MDefinition* operand, uint32_t defaultDepth,
                             const Uint32Vector& depths,
                             const DefVector& values) {
    if (inDeadCode()) {
      return true;
    }

    size_t numCases = depths.length();
    MOZ_ASSERT(numCases <= INT32_MAX);
    MOZ_ASSERT(numCases);

    MTableSwitch* table =
        MTableSwitch::New(alloc(), operand, 0, int32_t(numCases - 1));

    size_t defaultIndex;
    if (!table->addDefault(nullptr, &defaultIndex)) {
      return false;
    }
    if (!addControlFlowPatch(table, defaultDepth, defaultIndex)) {
      return false;
    }

    using IndexToCaseMap =
        HashMap<uint32_t, uint32_t, DefaultHasher<uint32_t>, SystemAllocPolicy>;

    IndexToCaseMap indexToCase;
    if (!indexToCase.put(defaultDepth, defaultIndex)) {
      return false;
    }

    for (size_t i = 0; i < numCases; i++) {
      if (!mirGen().ensureBallast()) {
        return false;
      }

      uint32_t depth = depths[i];

      size_t caseIndex;
      IndexToCaseMap::AddPtr p = indexToCase.lookupForAdd(depth);
      if (!p) {
        if (!table->addSuccessor(nullptr, &caseIndex)) {
          return false;
        }
        if (!addControlFlowPatch(table, depth, caseIndex)) {
          return false;
        }
        if (!indexToCase.add(p, depth, caseIndex)) {
          return false;
        }
      } else {
        caseIndex = p->value();
      }

      if (!table->addCase(caseIndex)) {
        return false;
      }
    }

    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(table);
    curBlock_ = nullptr;

    return true;
  }

  /********************************************************** Exceptions ***/

  bool inTryBlockFrom(uint32_t fromRelativeDepth,
                      uint32_t* tryRelativeDepth) const {
    uint32_t relativeDepth;
    if (iter_.controlFindInnermostFrom(
            [](LabelKind kind, const Control& control) {
              return control.tryControl != nullptr &&
                     control.tryControl->inBody;
            },
            fromRelativeDepth, &relativeDepth)) {
      *tryRelativeDepth = relativeDepth;
      return true;
    }

    if (callerCompiler_ && callerCompiler_->inTryCode()) {
      *tryRelativeDepth = iter_.controlStackDepth() - 1;
      return true;
    }

    return false;
  }

  bool inTryBlockFrom(uint32_t fromRelativeDepth,
                      ControlInstructionVector** landingPadPatches) {
    uint32_t tryRelativeDepth;
    if (!inTryBlockFrom(fromRelativeDepth, &tryRelativeDepth)) {
      return false;
    }

    if (tryRelativeDepth == iter().controlStackDepth() - 1) {
      *landingPadPatches = &bodyRethrowPadPatches_;
    } else {
      *landingPadPatches =
          &iter().controlItem(tryRelativeDepth).tryControl->landingPadPatches;
    }
    return true;
  }

  bool inTryBlock(ControlInstructionVector** landingPadPatches) {
    return inTryBlockFrom(0, landingPadPatches);
  }

  bool inTryCode() const {
    uint32_t tryRelativeDepth;
    return inTryBlockFrom(0, &tryRelativeDepth);
  }

  MDefinition* loadTag(uint32_t tagIndex) {
    MWasmLoadInstanceDataField* tag = MWasmLoadInstanceDataField::New(
        alloc(), MIRType::WasmAnyRef,
        codeMeta().offsetOfTagInstanceData(tagIndex), true, instancePointer_);
    curBlock_->add(tag);
    return tag;
  }

  void loadPendingExceptionState(MDefinition** pendingException,
                                 MDefinition** pendingExceptionTag) {
    auto* exception = MWasmLoadInstance::New(
        alloc(), instancePointer_, wasm::Instance::offsetOfPendingException(),
        MIRType::WasmAnyRef, AliasSet::Load(AliasSet::WasmPendingException));
    curBlock_->add(exception);
    *pendingException = exception;

    auto* tag = MWasmLoadInstance::New(
        alloc(), instancePointer_,
        wasm::Instance::offsetOfPendingExceptionTag(), MIRType::WasmAnyRef,
        AliasSet::Load(AliasSet::WasmPendingException));
    curBlock_->add(tag);
    *pendingExceptionTag = tag;
  }

  [[nodiscard]] bool setPendingExceptionState(MDefinition* exception,
                                              MDefinition* tag) {
    // Set the pending exception object
    auto* exceptionAddr = MWasmDerivedPointer::New(
        alloc(), instancePointer_, Instance::offsetOfPendingException());
    curBlock_->add(exceptionAddr);
    auto* setException = MWasmStoreRef::New(
        alloc(), instancePointer_, exceptionAddr, /*valueOffset=*/0, exception,
        AliasSet::WasmPendingException, WasmPreBarrierKind::Normal);
    curBlock_->add(setException);
    if (!postBarrierEdgePrecise(/*bytecodeOffset=*/0, exceptionAddr,
                                exception)) {
      return false;
    }

    // Set the pending exception tag object
    auto* exceptionTagAddr = MWasmDerivedPointer::New(
        alloc(), instancePointer_, Instance::offsetOfPendingExceptionTag());
    curBlock_->add(exceptionTagAddr);
    auto* setExceptionTag = MWasmStoreRef::New(
        alloc(), instancePointer_, exceptionTagAddr, /*valueOffset=*/0, tag,
        AliasSet::WasmPendingException, WasmPreBarrierKind::Normal);
    curBlock_->add(setExceptionTag);
    return postBarrierEdgePrecise(/*bytecodeOffset=*/0, exceptionTagAddr, tag);
  }

  [[nodiscard]] bool endWithPadPatch(
      ControlInstructionVector* tryLandingPadPatches) {
    MGoto* jumpToLandingPad = MGoto::New(alloc());
    curBlock_->end(jumpToLandingPad);
    return tryLandingPadPatches->emplaceBack(jumpToLandingPad);
  }

  [[nodiscard]] bool delegatePadPatches(const ControlInstructionVector& patches,
                                        uint32_t relativeDepth) {
    if (patches.empty()) {
      return true;
    }

    // Find where we are delegating the pad patches to.
    ControlInstructionVector* targetPatches;
    if (!inTryBlockFrom(relativeDepth, &targetPatches)) {
      MOZ_ASSERT(relativeDepth <= pendingBlockDepth_ - 1);
      targetPatches = &bodyRethrowPadPatches_;
    }

    // Append the delegate's pad patches to the target's.
    for (MControlInstruction* ins : patches) {
      if (!targetPatches->emplaceBack(ins)) {
        return false;
      }
    }
    return true;
  }

  [[nodiscard]]
  bool beginCatchableCall(CallCompileState* callState) {
    if (!inTryBlock(&callState->tryLandingPadPatches)) {
      MOZ_ASSERT(!callState->isCatchable());
      return true;
    }
    MOZ_ASSERT(callState->isCatchable());

    // Allocate a try note
    if (!rootCompiler_.addTryNote(&callState->tryNoteIndex)) {
      return false;
    }

    // Allocate blocks for fallthrough and exceptions
    return newBlock(curBlock_, &callState->fallthroughBlock) &&
           newBlock(curBlock_, &callState->prePadBlock);
  }

  [[nodiscard]]
  bool finishCatchableCall(CallCompileState* callState) {
    if (!callState->tryLandingPadPatches) {
      return true;
    }

    // Switch to the prePadBlock
    MBasicBlock* callBlock = curBlock_;
    curBlock_ = callState->prePadBlock;

    // Mark this as the landing pad for the call
    curBlock_->add(MWasmCallLandingPrePad::New(alloc(), callBlock,
                                               callState->tryNoteIndex));

    // End with a pending jump to the landing pad
    if (!endWithPadPatch(callState->tryLandingPadPatches)) {
      return false;
    }

    // Compilation continues in the fallthroughBlock.
    curBlock_ = callState->fallthroughBlock;
    return true;
  }

  // Create a landing pad for a try block. This is also used for the implicit
  // rethrow landing pad used for delegate instructions that target the
  // outermost label.
  [[nodiscard]]
  bool createTryLandingPad(ControlInstructionVector& landingPadPatches,
                           MBasicBlock** landingPad) {
    MOZ_ASSERT(!landingPadPatches.empty());

    // Bind the branches from exception throwing code to a new landing pad
    // block. This is done similarly to what is done in bindBranches.
    MControlInstruction* ins = landingPadPatches[0];
    MBasicBlock* pred = ins->block();
    if (!newBlock(pred, landingPad)) {
      return false;
    }
    ins->replaceSuccessor(MGoto::TargetIndex, *landingPad);
    for (size_t i = 1; i < landingPadPatches.length(); i++) {
      ins = landingPadPatches[i];
      pred = ins->block();
      if (!(*landingPad)->addPredecessor(alloc(), pred)) {
        return false;
      }
      ins->replaceSuccessor(MGoto::TargetIndex, *landingPad);
    }

    // Clear the now bound pad patches.
    landingPadPatches.clear();
    return true;
  }

  [[nodiscard]]
  bool createTryTableLandingPad(TryControl* tryControl) {
    // If there were no patches, then there were no throwing instructions and
    // we don't need to do anything.
    if (tryControl->landingPadPatches.empty()) {
      return true;
    }

    // Create the landing pad block and bind all the throwing instructions
    MBasicBlock* landingPad;
    if (!createTryLandingPad(tryControl->landingPadPatches, &landingPad)) {
      return false;
    }

    // Get the pending exception from the instance
    MDefinition* pendingException;
    MDefinition* pendingExceptionTag;
    if (!consumePendingException(&landingPad, &pendingException,
                                 &pendingExceptionTag)) {
      return false;
    }

    MBasicBlock* originalBlock = curBlock_;
    curBlock_ = landingPad;

    bool hadCatchAll = false;
    for (const TryTableCatch& tryTableCatch : tryControl->catches) {
      // Handle a catch_all by jumping to the target block
      if (tryTableCatch.tagIndex == CatchAllIndex) {
        // Capture the exnref value if we need to
        DefVector values;
        if (tryTableCatch.captureExnRef && !values.append(pendingException)) {
          return false;
        }

        // Branch to the catch_all code
        if (!br(tryTableCatch.labelRelativeDepth, values)) {
          return false;
        }

        // Break from the loop and skip the implicit rethrow that's needed
        // if we didn't have a catch_all
        hadCatchAll = true;
        break;
      }

      // Handle a tagged catch by doing a compare and branch on the tag index,
      // jumping to a catch block if they match, or else to a fallthrough block
      // to continue the landing pad.
      MBasicBlock* catchBlock = nullptr;
      MBasicBlock* fallthroughBlock = nullptr;
      if (!newBlock(curBlock_, &catchBlock) ||
          !newBlock(curBlock_, &fallthroughBlock)) {
        return false;
      }

      // Branch to the catch block if the exception's tag matches this catch
      // block's tag.
      MDefinition* catchTag = loadTag(tryTableCatch.tagIndex);
      MDefinition* matchesCatchTag =
          compare(pendingExceptionTag, catchTag, JSOp::Eq,
                  MCompare::Compare_WasmAnyRef);
      curBlock_->end(
          MTest::New(alloc(), matchesCatchTag, catchBlock, fallthroughBlock));

      // Set up the catch block by extracting the values from the exception
      // object.
      curBlock_ = catchBlock;

      // Extract the exception values for the catch block
      DefVector values;
      if (!loadExceptionValues(pendingException, tryTableCatch.tagIndex,
                               &values)) {
        return false;
      }
      if (tryTableCatch.captureExnRef && !values.append(pendingException)) {
        return false;
      }

      if (!br(tryTableCatch.labelRelativeDepth, values)) {
        return false;
      }

      curBlock_ = fallthroughBlock;
    }

    // If there was no catch_all, we must rethrow this exception.
    if (!hadCatchAll) {
      if (!throwFrom(pendingException, pendingExceptionTag)) {
        return false;
      }
    }

    curBlock_ = originalBlock;
    return true;
  }

  // Consume the pending exception state from instance. This will clear out the
  // previous value.
  [[nodiscard]]
  bool consumePendingException(MBasicBlock** landingPad,
                               MDefinition** pendingException,
                               MDefinition** pendingExceptionTag) {
    MBasicBlock* prevBlock = curBlock_;
    curBlock_ = *landingPad;

    // Load the pending exception and tag
    loadPendingExceptionState(pendingException, pendingExceptionTag);

    // Clear the pending exception and tag
    auto* null = constantNullRef(MaybeRefType());
    if (!setPendingExceptionState(null, null)) {
      return false;
    }

    // The landing pad may have changed from loading and clearing the pending
    // exception state.
    *landingPad = curBlock_;

    curBlock_ = prevBlock;
    return true;
  }

  [[nodiscard]] bool startTry() {
    Control& control = iter().controlItem();
    control.block = curBlock_;
    control.tryControl = rootCompiler_.newTryControl();
    if (!control.tryControl) {
      return false;
    }
    control.tryControl->inBody = true;
    return startBlock();
  }

  [[nodiscard]] bool startTryTable(TryTableCatchVector&& catches) {
    Control& control = iter().controlItem();
    control.block = curBlock_;
    control.tryControl = rootCompiler_.newTryControl();
    if (!control.tryControl) {
      return false;
    }
    control.tryControl->inBody = true;
    control.tryControl->catches = std::move(catches);
    return startBlock();
  }

  [[nodiscard]] bool joinTryOrCatchBlock(Control& control) {
    // If the try or catch block ended with dead code, there is no need to
    // do any control flow join.
    if (inDeadCode()) {
      return true;
    }

    // This is a split path which we'll need to join later, using a control
    // flow patch.
    MOZ_ASSERT(!curBlock_->hasLastIns());
    MGoto* jump = MGoto::New(alloc());
    if (!addControlFlowPatch(jump, 0, MGoto::TargetIndex)) {
      return false;
    }

    // Finish the current block with the control flow patch instruction.
    curBlock_->end(jump);
    return true;
  }

  // Finish the previous block (either a try or catch block) and then setup a
  // new catch block.
  [[nodiscard]] bool switchToCatch(Control& control, LabelKind fromKind,
                                   uint32_t tagIndex) {
    // Mark this control node as being no longer in the body of the try
    control.tryControl->inBody = false;

    // If there is no control block, then either:
    //   - the entry of the try block is dead code, or
    //   - there is no landing pad for the try-catch.
    // In either case, any catch will be dead code.
    if (!control.block) {
      MOZ_ASSERT(inDeadCode());
      return true;
    }

    // Join the previous try or catch block with a patch to the future join of
    // the whole try-catch block.
    if (!joinTryOrCatchBlock(control)) {
      return false;
    }

    // If we are switching from the try block, create the landing pad. This is
    // guaranteed to happen once and only once before processing catch blocks.
    if (fromKind == LabelKind::Try) {
      if (!control.tryControl->landingPadPatches.empty()) {
        // Create the landing pad block and bind all the throwing instructions
        MBasicBlock* padBlock = nullptr;
        if (!createTryLandingPad(control.tryControl->landingPadPatches,
                                 &padBlock)) {
          return false;
        }

        // Store the pending exception and tag on the control item for future
        // use in catch handlers.
        if (!consumePendingException(
                &padBlock, &control.tryControl->pendingException,
                &control.tryControl->pendingExceptionTag)) {
          return false;
        }

        // Set the control block for this try-catch to the landing pad.
        control.block = padBlock;
      } else {
        control.block = nullptr;
      }
    }

    // If there is no landing pad, then this and following catches are dead
    // code.
    if (!control.block) {
      curBlock_ = nullptr;
      return true;
    }

    // Switch to the landing pad.
    curBlock_ = control.block;

    // We should have a pending exception and tag if we were able to create a
    // landing pad.
    MOZ_ASSERT(control.tryControl->pendingException);
    MOZ_ASSERT(control.tryControl->pendingExceptionTag);

    // Handle a catch_all by immediately jumping to a new block. We require a
    // new block (as opposed to just emitting the catch_all code in the current
    // block) because rethrow requires the exception/tag to be present in the
    // landing pad's slots, while the catch_all block must not have the
    // exception/tag in slots.
    if (tagIndex == CatchAllIndex) {
      MBasicBlock* catchAllBlock = nullptr;
      if (!goToNewBlock(curBlock_, &catchAllBlock)) {
        return false;
      }
      // Compilation will continue in the catch_all block.
      curBlock_ = catchAllBlock;
      return true;
    }

    // Handle a tagged catch by doing a compare and branch on the tag index,
    // jumping to a catch block if they match, or else to a fallthrough block
    // to continue the landing pad.
    MBasicBlock* catchBlock = nullptr;
    MBasicBlock* fallthroughBlock = nullptr;
    if (!newBlock(curBlock_, &catchBlock) ||
        !newBlock(curBlock_, &fallthroughBlock)) {
      return false;
    }

    // Branch to the catch block if the exception's tag matches this catch
    // block's tag.
    MDefinition* catchTag = loadTag(tagIndex);
    MDefinition* matchesCatchTag =
        compare(control.tryControl->pendingExceptionTag, catchTag, JSOp::Eq,
                MCompare::Compare_WasmAnyRef);
    curBlock_->end(
        MTest::New(alloc(), matchesCatchTag, catchBlock, fallthroughBlock));

    // The landing pad will continue in the fallthrough block
    control.block = fallthroughBlock;

    // Set up the catch block by extracting the values from the exception
    // object.
    curBlock_ = catchBlock;

    // Extract the exception values for the catch block
    DefVector values;
    if (!loadExceptionValues(control.tryControl->pendingException, tagIndex,
                             &values)) {
      return false;
    }
    iter().setResults(values.length(), values);
    return true;
  }

  [[nodiscard]] bool loadExceptionValues(MDefinition* exception,
                                         uint32_t tagIndex, DefVector* values) {
    SharedTagType tagType = codeMeta().tags[tagIndex].type;
    const ValTypeVector& params = tagType->argTypes();
    const TagOffsetVector& offsets = tagType->exceptionArgOffsets();

    // Get the data pointer from the exception object
    auto* data = MWasmLoadField::New(
        alloc(), exception, nullptr, WasmExceptionObject::offsetOfData(),
        mozilla::Nothing(), MIRType::Pointer, MWideningOp::None,
        AliasSet::Load(AliasSet::Any));
    if (!data) {
      return false;
    }
    curBlock_->add(data);

    // Presize the values vector to the number of params
    if (!values->reserve(params.length())) {
      return false;
    }

    // Load each value from the data pointer
    for (size_t i = 0; i < params.length(); i++) {
      if (!mirGen().ensureBallast()) {
        return false;
      }
      auto* load =
          MWasmLoadField::New(alloc(), data, exception, offsets[i],
                              mozilla::Nothing(), params[i].toMIRType(),
                              MWideningOp::None, AliasSet::Load(AliasSet::Any),
                              mozilla::Nothing(), params[i].toMaybeRefType());
      if (!load || !values->append(load)) {
        return false;
      }
      curBlock_->add(load);
    }
    return true;
  }

  [[nodiscard]] bool finishTryCatch(LabelKind kind, Control& control,
                                    DefVector* defs) {
    switch (kind) {
      case LabelKind::Try: {
        // This is a catchless try, we must delegate all throwing instructions
        // to the nearest enclosing try block if one exists, or else to the
        // body block which will handle it in emitBodyRethrowPad. We
        // specify a relativeDepth of '1' to delegate outside of the still
        // active try block.
        uint32_t relativeDepth = 1;
        if (!delegatePadPatches(control.tryControl->landingPadPatches,
                                relativeDepth)) {
          return false;
        }
        break;
      }
      case LabelKind::Catch: {
        MOZ_ASSERT(!control.tryControl->inBody);
        // This is a try without a catch_all, we must have a rethrow at the end
        // of the landing pad (if any).
        MBasicBlock* padBlock = control.block;
        if (padBlock) {
          MBasicBlock* prevBlock = curBlock_;
          curBlock_ = padBlock;
          if (!throwFrom(control.tryControl->pendingException,
                         control.tryControl->pendingExceptionTag)) {
            return false;
          }
          curBlock_ = prevBlock;
        }
        break;
      }
      case LabelKind::CatchAll: {
        MOZ_ASSERT(!control.tryControl->inBody);
        // This is a try with a catch_all, and requires no special handling.
        break;
      }
      default:
        MOZ_CRASH();
    }

    // Finish the block, joining the try and catch blocks
    return finishBlock(defs);
  }

  [[nodiscard]] bool finishTryTable(Control& control, DefVector* defs) {
    // Mark this control as no longer in the body of the try
    control.tryControl->inBody = false;
    // Create a landing pad for all of the catches
    if (!createTryTableLandingPad(control.tryControl.get())) {
      return false;
    }
    // Finish the block, joining the try and catch blocks
    return finishBlock(defs);
  }

  [[nodiscard]] bool emitBodyRethrowPad(Control& control) {
    // If there are no throwing instructions pending, we don't need to do
    // anything
    if (bodyRethrowPadPatches_.empty()) {
      return true;
    }

    // Create a landing pad for any throwing instructions
    MBasicBlock* padBlock;
    if (!createTryLandingPad(bodyRethrowPadPatches_, &padBlock)) {
      return false;
    }

    // If we're inlined into another function, we save the landing pad to be
    // linked later directly to our caller's landing pad. See
    // `finishedInlinedCallDirect`.
    if (callerCompiler_ && callerCompiler_->inTryCode()) {
      pendingInlineCatchBlock_ = padBlock;
      return true;
    }

    // Otherwise we need to grab the pending exception and rethrow it.
    MDefinition* pendingException;
    MDefinition* pendingExceptionTag;
    if (!consumePendingException(&padBlock, &pendingException,
                                 &pendingExceptionTag)) {
      return false;
    }

    // Switch to the landing pad and rethrow the exception
    MBasicBlock* prevBlock = curBlock_;
    curBlock_ = padBlock;
    if (!throwFrom(pendingException, pendingExceptionTag)) {
      return false;
    }
    curBlock_ = prevBlock;

    MOZ_ASSERT(bodyRethrowPadPatches_.empty());
    return true;
  }

  [[nodiscard]] bool emitNewException(MDefinition* tag,
                                      MDefinition** exception) {
    return emitInstanceCall1(readBytecodeOffset(), SASigExceptionNew, tag,
                             exception);
  }

  [[nodiscard]] bool emitThrow(uint32_t tagIndex, const DefVector& argValues) {
    if (inDeadCode()) {
      return true;
    }
    uint32_t bytecodeOffset = readBytecodeOffset();

    // Load the tag
    MDefinition* tag = loadTag(tagIndex);
    if (!tag) {
      return false;
    }

    // Allocate an exception object
    MDefinition* exception;
    if (!emitNewException(tag, &exception)) {
      return false;
    }

    // Load the data pointer from the object
    auto* data = MWasmLoadField::New(
        alloc(), exception, nullptr, WasmExceptionObject::offsetOfData(),
        mozilla::Nothing(), MIRType::Pointer, MWideningOp::None,
        AliasSet::Load(AliasSet::Any));
    if (!data) {
      return false;
    }
    curBlock_->add(data);

    // Store the params into the data pointer
    SharedTagType tagType = codeMeta().tags[tagIndex].type;
    for (size_t i = 0; i < tagType->exceptionArgOffsets().length(); i++) {
      if (!mirGen().ensureBallast()) {
        return false;
      }
      ValType type = tagType->argTypes()[i];
      uint32_t offset = tagType->exceptionArgOffsets()[i];

      if (!type.isRefRepr()) {
        auto* store = MWasmStoreField::New(
            alloc(), data, exception, offset, mozilla::Nothing(), argValues[i],
            MNarrowingOp::None, AliasSet::Store(AliasSet::Any));
        if (!store) {
          return false;
        }
        curBlock_->add(store);
        continue;
      }

      // Store the new value
      auto* store = MWasmStoreFieldRef::New(
          alloc(), instancePointer_, data, exception, offset,
          mozilla::Nothing(), argValues[i], AliasSet::Store(AliasSet::Any),
          Nothing(), WasmPreBarrierKind::None);
      if (!store) {
        return false;
      }
      curBlock_->add(store);

      // Call the post-write barrier
      if (!postBarrierWholeCell(bytecodeOffset, exception, argValues[i])) {
        return false;
      }
    }

    // Throw the exception
    return throwFrom(exception, tag);
  }

  [[nodiscard]] bool emitThrowRef(MDefinition* exnRef) {
    if (inDeadCode()) {
      return true;
    }

    // The exception must be non-null
    exnRef = refAsNonNull(exnRef);
    if (!exnRef) {
      return false;
    }

    // Call Instance::throwException to perform tag unpacking and throw the
    // exception
    if (!emitInstanceCall1(readBytecodeOffset(), SASigThrowException, exnRef)) {
      return false;
    }
    unreachableTrap();

    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]] bool throwFrom(MDefinition* exn, MDefinition* tag) {
    if (inDeadCode()) {
      return true;
    }

    // Check if there is a local catching try control, and if so, then add a
    // pad-patch to its tryPadPatches.
    ControlInstructionVector* tryLandingPadPatches;
    if (inTryBlock(&tryLandingPadPatches)) {
      // Set the pending exception state, the landing pad will read from this
      if (!setPendingExceptionState(exn, tag)) {
        return false;
      }

      // End with a pending jump to the landing pad
      if (!endWithPadPatch(tryLandingPadPatches)) {
        return false;
      }
      curBlock_ = nullptr;
      return true;
    }

    // If there is no surrounding catching block, call an instance method to
    // throw the exception.
    if (!emitInstanceCall1(readBytecodeOffset(), SASigThrowException, exn)) {
      return false;
    }
    unreachableTrap();

    curBlock_ = nullptr;
    return true;
  }

  [[nodiscard]] bool emitRethrow(uint32_t relativeDepth) {
    if (inDeadCode()) {
      return true;
    }

    Control& control = iter().controlItem(relativeDepth);
    MOZ_ASSERT(iter().controlKind(relativeDepth) == LabelKind::Catch ||
               iter().controlKind(relativeDepth) == LabelKind::CatchAll);
    return throwFrom(control.tryControl->pendingException,
                     control.tryControl->pendingExceptionTag);
  }

  /******************************** WasmGC: low level load/store helpers ***/

  // Given a (StorageType, FieldExtension) pair, produce the (MIRType,
  // MWideningOp) pair that will give the correct operation for reading the
  // value from memory.
  static void fieldLoadInfoToMIR(StorageType type, FieldWideningOp wideningOp,
                                 MIRType* mirType, MWideningOp* mirWideningOp) {
    switch (type.kind()) {
      case StorageType::I8: {
        switch (wideningOp) {
          case FieldWideningOp::Signed:
            *mirType = MIRType::Int32;
            *mirWideningOp = MWideningOp::FromS8;
            return;
          case FieldWideningOp::Unsigned:
            *mirType = MIRType::Int32;
            *mirWideningOp = MWideningOp::FromU8;
            return;
          default:
            MOZ_CRASH();
        }
      }
      case StorageType::I16: {
        switch (wideningOp) {
          case FieldWideningOp::Signed:
            *mirType = MIRType::Int32;
            *mirWideningOp = MWideningOp::FromS16;
            return;
          case FieldWideningOp::Unsigned:
            *mirType = MIRType::Int32;
            *mirWideningOp = MWideningOp::FromU16;
            return;
          default:
            MOZ_CRASH();
        }
      }
      default: {
        switch (wideningOp) {
          case FieldWideningOp::None:
            *mirType = type.toMIRType();
            *mirWideningOp = MWideningOp::None;
            return;
          default:
            MOZ_CRASH();
        }
      }
    }
  }

  // Given a StorageType, return the Scale required when accessing array
  // elements of this type.
  static Scale scaleFromFieldType(StorageType type) {
    if (type.kind() == StorageType::V128) {
      // V128 is accessed differently, so this scale will not be used.
      return Scale::Invalid;
    }
    return ShiftToScale(type.indexingShift());
  }

  // Given a StorageType, produce the MNarrowingOp required for writing the
  // value to memory.
  static MNarrowingOp fieldStoreInfoToMIR(StorageType type) {
    switch (type.kind()) {
      case StorageType::I8:
        return MNarrowingOp::To8;
      case StorageType::I16:
        return MNarrowingOp::To16;
      default:
        return MNarrowingOp::None;
    }
  }

  // Generate a write of `value` at address `base + offset`, where `offset` is
  // known at JIT time.  If the written value is a reftype, the previous value
  // at `base + offset` will be retrieved and handed off to the post-write
  // barrier.  `keepAlive` will be referenced by the instruction so as to hold
  // it live (from the GC's point of view).
  [[nodiscard]] bool writeGcValueAtBasePlusOffset(
      uint32_t bytecodeOffset, StorageType type, MDefinition* keepAlive,
      AliasSet::Flag aliasBitset, MDefinition* value, MDefinition* base,
      uint32_t offset, uint32_t fieldIndex, bool needsTrapInfo,
      WasmPreBarrierKind preBarrierKind, WasmPostBarrierKind postBarrierKind) {
    MOZ_ASSERT(aliasBitset != 0);
    MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
    MOZ_ASSERT(type.widenToValType().toMIRType() == value->type());
    MNarrowingOp narrowingOp = fieldStoreInfoToMIR(type);

    if (!type.isRefRepr()) {
      MaybeTrapSiteDesc maybeTrap;
      if (needsTrapInfo) {
        maybeTrap.emplace(trapSiteDesc());
      }

      auto* store = MWasmStoreField::New(
          alloc(), base, keepAlive, offset, mozilla::Some(fieldIndex), value,
          narrowingOp, AliasSet::Store(aliasBitset), maybeTrap);
      if (!store) {
        return false;
      }
      curBlock_->add(store);
      return true;
    }

    // Otherwise it's a ref store.  Load the previous value so we can show it
    // to the post-write barrier.
    //
    // Optimisation opportunity: for the case where this field write results
    // from struct.new, the old value is always zero.  So we should synthesise
    // a suitable zero constant rather than reading it from the object.  See
    // also bug 1799999.
    MOZ_ASSERT(narrowingOp == MNarrowingOp::None);
    MOZ_ASSERT(type.widenToValType() == type.valType());

    // Store the new value
    auto* store = MWasmStoreFieldRef::New(
        alloc(), instancePointer_, base, keepAlive, offset,
        mozilla::Some(fieldIndex), value, AliasSet::Store(aliasBitset),
        mozilla::Some(trapSiteDesc()), preBarrierKind);
    if (!store) {
      return false;
    }
    curBlock_->add(store);

    // Call the post-write barrier
    switch (postBarrierKind) {
      case WasmPostBarrierKind::WholeCell:
        return postBarrierWholeCell(bytecodeOffset, keepAlive, value);
      case WasmPostBarrierKind::Edge:
        MOZ_CRASH("WasmPostBarrierKind::Edge not supported");
      case WasmPostBarrierKind::None:
        return true;
      default:
        MOZ_CRASH("Bad postBarrierKind");
    }
  }

  // Generate a write of `value` at address `base + index * scale`, where
  // `scale` is known at JIT-time.  If the written value is a reftype, the
  // previous value at `base + index * scale` will be retrieved and handed off
  // to the post-write barrier.  `keepAlive` will be referenced by the
  // instruction so as to hold it live (from the GC's point of view).
  [[nodiscard]] bool writeGcValueAtBasePlusScaledIndex(
      uint32_t bytecodeOffset, StorageType type, MDefinition* keepAlive,
      AliasSet::Flag aliasBitset, MDefinition* value, MDefinition* base,
      uint32_t scale, MDefinition* index, WasmPreBarrierKind preBarrierKind,
      WasmPostBarrierKind postBarrierKind) {
    MOZ_ASSERT(aliasBitset != 0);
    MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
    MOZ_ASSERT(type.widenToValType().toMIRType() == value->type());
    MOZ_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8 ||
               scale == 16);

    MNarrowingOp narrowingOp = fieldStoreInfoToMIR(type);

    if (!type.isRefRepr()) {
      MaybeTrapSiteDesc maybeTrap;
      Scale scale = scaleFromFieldType(type);
      auto* store = MWasmStoreElement::New(
          alloc(), base, index, value, keepAlive, narrowingOp, scale,
          AliasSet::Store(aliasBitset), maybeTrap);
      if (!store) {
        return false;
      }
      curBlock_->add(store);
      return true;
    }

    // Otherwise it's a ref store.
    MOZ_ASSERT(narrowingOp == MNarrowingOp::None);
    MOZ_ASSERT(type.widenToValType() == type.valType());

    // Store the new value
    auto* store = MWasmStoreElementRef::New(
        alloc(), instancePointer_, base, index, value, keepAlive,
        AliasSet::Store(aliasBitset), mozilla::Some(trapSiteDesc()),
        preBarrierKind);
    if (!store) {
      return false;
    }
    curBlock_->add(store);

    switch (postBarrierKind) {
      case WasmPostBarrierKind::WholeCell:
        return postBarrierWholeCell(bytecodeOffset, keepAlive, value);
      case WasmPostBarrierKind::Edge:
        return postBarrierEdgeAtIndex(bytecodeOffset, keepAlive, base, index,
                                      sizeof(void*), value);
      case WasmPostBarrierKind::None:
        return true;
      default:
        MOZ_CRASH("Bad postBarrierKind");
    }
  }

  // Generate a read from address `base + offset`, where `offset` is known at
  // JIT time.  The loaded value will be widened as described by `type` and
  // `fieldWideningOp`.  `keepAlive` will be referenced by the instruction so as
  // to hold it live (from the GC's point of view).
  [[nodiscard]] MDefinition* readGcValueAtBasePlusOffset(
      StorageType type, FieldWideningOp fieldWideningOp, MDefinition* keepAlive,
      AliasSet::Flag aliasBitset, MDefinition* base, uint32_t offset,
      uint32_t fieldIndex, bool needsTrapInfo) {
    MOZ_ASSERT(aliasBitset != 0);
    MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
    MIRType mirType;
    MWideningOp mirWideningOp;
    fieldLoadInfoToMIR(type, fieldWideningOp, &mirType, &mirWideningOp);
    MaybeTrapSiteDesc maybeTrap;
    if (needsTrapInfo) {
      maybeTrap.emplace(trapSiteDesc());
    }

    auto* load = MWasmLoadField::New(alloc(), base, keepAlive, offset,
                                     mozilla::Some(fieldIndex), mirType,
                                     mirWideningOp, AliasSet::Load(aliasBitset),
                                     maybeTrap, type.toMaybeRefType());
    if (!load) {
      return nullptr;
    }
    curBlock_->add(load);
    return load;
  }

  // Generate a read from address `base + index * scale`, where `scale` is
  // known at JIT-time.  The loaded value will be widened as described by
  // `type` and `fieldWideningOp`.  `keepAlive` will be referenced by the
  // instruction so as to hold it live (from the GC's point of view).
  [[nodiscard]] MDefinition* readGcArrayValueAtIndex(
      StorageType type, FieldWideningOp fieldWideningOp, MDefinition* keepAlive,
      AliasSet::Flag aliasBitset, MDefinition* base, MDefinition* index) {
    MOZ_ASSERT(aliasBitset != 0);
    MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);

    MIRType mirType;
    MWideningOp mirWideningOp;
    fieldLoadInfoToMIR(type, fieldWideningOp, &mirType, &mirWideningOp);
    Scale scale = scaleFromFieldType(type);
    auto* load = MWasmLoadElement::New(
        alloc(), base, keepAlive, index, mirType, mirWideningOp, scale,
        AliasSet::Load(aliasBitset), mozilla::Some(trapSiteDesc()),
        type.toMaybeRefType());
    if (!load) {
      return nullptr;
    }
    curBlock_->add(load);
    return load;
  }

  /************************************************ WasmGC: type helpers ***/

  // Returns an MDefinition holding the supertype vector for `typeIndex`.
  [[nodiscard]] MDefinition* loadSuperTypeVector(uint32_t typeIndex) {
    uint32_t stvOffset = codeMeta().offsetOfSuperTypeVector(typeIndex);

    auto* load =
        MWasmLoadInstanceDataField::New(alloc(), MIRType::Pointer, stvOffset,
                                        /*isConst=*/true, instancePointer_);
    if (!load) {
      return nullptr;
    }
    curBlock_->add(load);
    return load;
  }

  uint32_t readAllocSiteIndex(uint32_t typeIndex) {
    if (!codeTailMeta() || !codeTailMeta()->hasFuncDefAllocSites()) {
      // For single tier of optimized compilation, there are no assigned alloc
      // sites, using type index as alloc site.
      return typeIndex;
    }
    AllocSitesRange rangeInModule =
        codeTailMeta()->getFuncDefAllocSites(funcIndex());
    uint32_t localIndex = numAllocSites_++;
    MOZ_RELEASE_ASSERT(localIndex < rangeInModule.length);
    return rangeInModule.begin + localIndex;
  }

  [[nodiscard]] MDefinition* loadAllocSiteInstanceData(
      uint32_t allocSiteIndex) {
    auto* allocSites = MWasmLoadInstance::New(
        alloc(), instancePointer_, Instance::offsetOfAllocSites(),
        MIRType::Pointer, AliasSet::None());
    if (!allocSites) {
      return nullptr;
    }
    curBlock_->add(allocSites);

    auto* result = MWasmDerivedPointer::New(
        alloc(), allocSites, allocSiteIndex * sizeof(gc::AllocSite));
    if (!result) {
      return nullptr;
    }
    curBlock_->add(result);
    return result;
  }

  /********************************************** WasmGC: struct helpers ***/

  [[nodiscard]] MDefinition* createStructObject(uint32_t typeIndex,
                                                uint32_t allocSiteIndex,
                                                bool zeroFields) {
    // Allocate an uninitialized struct.
    MDefinition* allocSite = loadAllocSiteInstanceData(allocSiteIndex);
    if (!allocSite) {
      return nullptr;
    }

    const TypeDef* typeDef = &(*codeMeta().types)[typeIndex];
    auto* structObject =
        MWasmNewStructObject::New(alloc(), instancePointer_, allocSite, typeDef,
                                  zeroFields, trapSiteDesc());
    if (!structObject) {
      return nullptr;
    }
    curBlock_->add(structObject);

    return structObject;
  }

  // Helper function for EmitStruct{New,Set}: given a MIR pointer to a
  // WasmStructObject, a MIR pointer to a value, and a field descriptor,
  // generate MIR to write the value to the relevant field in the object.
  [[nodiscard]] bool writeValueToStructField(
      uint32_t bytecodeOffset, const StructType& structType,
      uint32_t fieldIndex, MDefinition* structObject, MDefinition* value,
      WasmPreBarrierKind preBarrierKind) {
    StorageType fieldType = structType.fields_[fieldIndex].type;
    FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
    uint32_t areaOffset = path.hasOOL() ? path.oolOffset() : path.ilOffset();

    // Make `base` point at the first byte of either the struct object as a
    // whole or of the out-of-line data area.
    MDefinition* base;
    bool needsTrapInfo;
    if (path.hasOOL()) {
      // The path has two components, of which the first (the IL component) is
      // the offset where the OOL pointer is stored.  Hence `path.ilOffset()`.
      auto* loadDataPointer = MWasmLoadField::New(
          alloc(), structObject, nullptr, path.ilOffset(), mozilla::Nothing(),
          MIRType::WasmStructData, MWideningOp::None,
          AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
          mozilla::Some(trapSiteDesc()));
      if (!loadDataPointer) {
        return false;
      }
      curBlock_->add(loadDataPointer);
      base = loadDataPointer;
      needsTrapInfo = false;
    } else {
      base = structObject;
      needsTrapInfo = true;
    }
    // The transaction is to happen at `base + areaOffset`, so to speak.

    // The alias set denoting the field's location, although lacking a
    // Load-vs-Store indication at this point.
    AliasSet::Flag fieldAliasSet = path.hasOOL()
                                       ? AliasSet::WasmStructOutlineDataArea
                                       : AliasSet::WasmStructInlineDataArea;

    return writeGcValueAtBasePlusOffset(
        bytecodeOffset, fieldType, structObject, fieldAliasSet, value, base,
        areaOffset, fieldIndex, needsTrapInfo, preBarrierKind,
        WasmPostBarrierKind::WholeCell);
  }

  // Helper function for EmitStructGet: given a MIR pointer to a
  // WasmStructObject, a field descriptor and a field widening operation,
  // generate MIR to read the value from the relevant field in the object.
  [[nodiscard]] MDefinition* readValueFromStructField(
      const StructType& structType, uint32_t fieldIndex,
      FieldWideningOp wideningOp, MDefinition* structObject) {
    StorageType fieldType = structType.fields_[fieldIndex].type;
    FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
    uint32_t areaOffset = path.hasOOL() ? path.oolOffset() : path.ilOffset();

    // Make `base` point at the first byte of either the struct object as a
    // whole or of the out-of-line data area.
    MDefinition* base;
    bool needsTrapInfo;
    if (path.hasOOL()) {
      // The path has two components, of which the first (the IL component) is
      // the offset where the OOL pointer is stored.  Hence `path.ilOffset()`.
      auto* loadDataPointer = MWasmLoadField::New(
          alloc(), structObject, nullptr, path.ilOffset(), mozilla::Nothing(),
          MIRType::WasmStructData, MWideningOp::None,
          AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
          mozilla::Some(trapSiteDesc()));
      if (!loadDataPointer) {
        return nullptr;
      }
      curBlock_->add(loadDataPointer);
      base = loadDataPointer;
      needsTrapInfo = false;
    } else {
      base = structObject;
      needsTrapInfo = true;
    }
    // The transaction is to happen at `base + areaOffset`, so to speak.

    // The alias set denoting the field's location, although lacking a
    // Load-vs-Store indication at this point.
    AliasSet::Flag fieldAliasSet = path.hasOOL()
                                       ? AliasSet::WasmStructOutlineDataArea
                                       : AliasSet::WasmStructInlineDataArea;

    return readGcValueAtBasePlusOffset(fieldType, wideningOp, structObject,
                                       fieldAliasSet, base, areaOffset,
                                       fieldIndex, needsTrapInfo);
  }

  /********************************* WasmGC: address-arithmetic helpers ***/

  inline bool targetIs64Bit() const {
#ifdef JS_64BIT
    return true;
#else
    return false;
#endif
  }

  // Generate MIR to unsigned widen `val` out to the target word size.  If
  // `val` is already at the target word size, this is a no-op.  The only
  // other allowed case is where `val` is Int32 and we're compiling for a
  // 64-bit target, in which case a widen is generated.
  [[nodiscard]] MDefinition* unsignedWidenToTargetWord(MDefinition* val) {
    if (targetIs64Bit()) {
      if (val->type() == MIRType::Int32) {
        auto* ext = MExtendInt32ToInt64::New(alloc(), val, /*isUnsigned=*/true);
        if (!ext) {
          return nullptr;
        }
        curBlock_->add(ext);
        return ext;
      }
      MOZ_ASSERT(val->type() == MIRType::Int64);
      return val;
    }
    MOZ_ASSERT(val->type() == MIRType::Int32);
    return val;
  }

  /********************************************** WasmGC: array helpers ***/

  // Given `arrayObject`, the address of a WasmArrayObject, generate MIR to
  // return the contents of the WasmArrayObject::numElements_ field.
  // Adds trap site info for the null check.
  [[nodiscard]] MDefinition* getWasmArrayObjectNumElements(
      MDefinition* arrayObject) {
    MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);

    auto* numElements = MWasmLoadField::New(
        alloc(), arrayObject, nullptr, WasmArrayObject::offsetOfNumElements(),
        mozilla::Nothing(), MIRType::Int32, MWideningOp::None,
        AliasSet::Load(AliasSet::WasmArrayNumElements),
        mozilla::Some(trapSiteDesc()));
    if (!numElements) {
      return nullptr;
    }
    curBlock_->add(numElements);

    return numElements;
  }

  // Given `arrayObject`, the address of a WasmArrayObject, generate MIR to
  // return the contents of the WasmArrayObject::data_ field.
  [[nodiscard]] MDefinition* getWasmArrayObjectData(MDefinition* arrayObject) {
    MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);

    auto* data = MWasmLoadField::New(
        alloc(), arrayObject, nullptr, WasmArrayObject::offsetOfData(),
        mozilla::Nothing(), MIRType::WasmArrayData, MWideningOp::None,
        AliasSet::Load(AliasSet::WasmArrayDataPointer),
        mozilla::Some(trapSiteDesc()));
    if (!data) {
      return nullptr;
    }
    curBlock_->add(data);

    return data;
  }

  // Given a JIT-time-known type index `typeIndex` and a run-time known number
  // of elements `numElements`, create MIR to allocate a new wasm array,
  // possibly initialized with `typeIndex`s default value.
  [[nodiscard]] MDefinition* createArrayObject(uint32_t typeIndex,
                                               uint32_t allocSiteIndex,
                                               MDefinition* numElements,
                                               bool zeroFields) {
    MDefinition* allocSite = loadAllocSiteInstanceData(allocSiteIndex);
    if (!allocSite) {
      return nullptr;
    }

    const TypeDef* typeDef = &(*codeMeta().types)[typeIndex];
    auto* arrayObject = MWasmNewArrayObject::New(
        alloc(), instancePointer_, numElements, allocSite, typeDef, zeroFields,
        trapSiteDesc());
    if (!arrayObject) {
      return nullptr;
    }
    curBlock_->add(arrayObject);

    return arrayObject;
  }

  // This emits MIR to perform several actions common to array loads and
  // stores.  Given `arrayObject`, that points to a WasmArrayObject, and an
  // index value `index`, it:
  //
  // * Generates a trap if the array pointer is null
  // * Gets the size of the array
  // * Emits a bounds check of `index` against the array size
  // * Retrieves the OOL object pointer from the array
  // * Includes check for null via signal handler.
  //
  // The returned value is for the OOL object pointer.
  [[nodiscard]] MDefinition* setupForArrayAccess(MDefinition* arrayObject,
                                                 MDefinition* index) {
    MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);
    MOZ_ASSERT(index->type() == MIRType::Int32);

    // Check for null is done in getWasmArrayObjectNumElements.

    // Get the size value for the array.
    MDefinition* numElements = getWasmArrayObjectNumElements(arrayObject);
    if (!numElements) {
      return nullptr;
    }

    // Create a bounds check.
    auto* boundsCheck =
        MWasmBoundsCheck::New(alloc(), index, numElements, trapSiteDesc(),
                              MWasmBoundsCheck::Target::Other);
    if (!boundsCheck) {
      return nullptr;
    }
    curBlock_->add(boundsCheck);

    // Get the address of the first byte of the (OOL) data area.
    return getWasmArrayObjectData(arrayObject);
  }

  [[nodiscard]] bool fillArray(uint32_t bytecodeOffset,
                               const ArrayType& arrayType,
                               MDefinition* arrayObject, MDefinition* index,
                               MDefinition* numElements, MDefinition* val,
                               WasmPreBarrierKind preBarrierKind,
                               WasmPostBarrierKind postBarrierKind) {
    mozilla::DebugOnly<MIRType> valMIRType = val->type();
    StorageType elemType = arrayType.elementType();
    MOZ_ASSERT(elemType.widenToValType().toMIRType() == valMIRType);

    uint32_t elemSize = elemType.size();
    MOZ_ASSERT(elemSize >= 1 && elemSize <= 16);

    // Make `arrayBase` point at the first byte of the (OOL) data area.
    MDefinition* arrayBase = getWasmArrayObjectData(arrayObject);
    if (!arrayBase) {
      return false;
    }

    // We have:
    //   arrayBase   : TargetWord
    //   index       : Int32
    //   numElements : Int32
    //   val         : <any StorageType>
    //   $elemSize = arrayType.elementType_.size(); 1, 2, 4, 8 or 16
    //
    // Generate MIR:
    //   <in current block>
    //     limit : Int32 = index + numElements
    //     if (limit == index) goto after; // skip loop if trip count == 0
    //   loop:
    //     indexPhi = phi(index, indexNext)
    //     arrayBase[index * $elemSize] = val
    //     indexNext = indexPhi + 1
    //     if (indexNext <u limit) goto loop;
    //   after:
    //
    // We construct the loop "manually" rather than using
    // FunctionCompiler::{startLoop,closeLoop} as the latter have awareness of
    // the wasm view of loops, whereas the loop we're building here is not a
    // wasm-level loop.
    // ==== Create the "loop" and "after" blocks ====
    MBasicBlock* loopBlock;
    if (!newBlock(curBlock_, &loopBlock, MBasicBlock::LOOP_HEADER)) {
      return false;
    }
    MBasicBlock* afterBlock;
    if (!newBlock(loopBlock, &afterBlock)) {
      return false;
    }

    // ==== Fill in the remainder of the block preceding the loop ====
    MAdd* limit = MAdd::NewWasm(alloc(), index, numElements, MIRType::Int32);
    if (!limit) {
      return false;
    }
    curBlock_->add(limit);

    // Note: the comparison (and eventually the entire initialisation loop) will
    // be folded out in the case where the number of elements is zero.
    // See MCompare::tryFoldEqualOperands.
    MDefinition* limitEqualsBase =
        compare(limit, index, JSOp::StrictEq, MCompare::Compare_UInt32);
    if (!limitEqualsBase) {
      return false;
    }
    MTest* skipIfLimitEqualsBase =
        MTest::New(alloc(), limitEqualsBase, afterBlock, loopBlock);
    if (!skipIfLimitEqualsBase) {
      return false;
    }
    curBlock_->end(skipIfLimitEqualsBase);
    if (!afterBlock->addPredecessor(alloc(), curBlock_)) {
      return false;
    }

    // ==== Fill in the loop block as best we can ====
    curBlock_ = loopBlock;
    MPhi* indexPhi = MPhi::New(alloc(), MIRType::Int32);
    if (!indexPhi) {
      return false;
    }
    if (!indexPhi->reserveLength(2)) {
      return false;
    }
    indexPhi->addInput(index);
    curBlock_->addPhi(indexPhi);
    curBlock_->setLoopDepth(rootCompiler_.loopDepth() + 1);

    if (!writeGcValueAtBasePlusScaledIndex(
            bytecodeOffset, elemType, arrayObject, AliasSet::WasmArrayDataArea,
            val, arrayBase, elemSize, indexPhi, preBarrierKind,
            postBarrierKind)) {
      return false;
    }

    auto* indexNext =
        MAdd::NewWasm(alloc(), indexPhi, constantI32(1), MIRType::Int32);
    if (!indexNext) {
      return false;
    }
    curBlock_->add(indexNext);
    indexPhi->addInput(indexNext);

    MDefinition* indexNextLtuLimit =
        compare(indexNext, limit, JSOp::Lt, MCompare::Compare_UInt32);
    if (!indexNextLtuLimit) {
      return false;
    }
    auto* continueIfIndexNextLtuLimit =
        MTest::New(alloc(), indexNextLtuLimit, loopBlock, afterBlock);
    if (!continueIfIndexNextLtuLimit) {
      return false;
    }
    curBlock_->end(continueIfIndexNextLtuLimit);
    if (!loopBlock->addPredecessor(alloc(), loopBlock)) {
      return false;
    }
    // ==== Loop block completed ====

    curBlock_ = afterBlock;
    return true;
  }

  [[nodiscard]] bool createArrayCopy(uint32_t bytecodeOffset,
                                     MDefinition* dstArrayObject,
                                     MDefinition* dstArrayIndex,
                                     MDefinition* srcArrayObject,
                                     MDefinition* srcArrayIndex,
                                     MDefinition* numElements, int32_t elemSize,
                                     bool elemsAreRefTyped) {
    // Check for null is done in getWasmArrayObjectNumElements.

    // Get the arrays' actual sizes.
    MDefinition* dstNumElements = getWasmArrayObjectNumElements(dstArrayObject);
    if (!dstNumElements) {
      return false;
    }
    MDefinition* srcNumElements = getWasmArrayObjectNumElements(srcArrayObject);
    if (!srcNumElements) {
      return false;
    }

    // Create the bounds checks.
    MInstruction* dstBoundsCheck = MWasmBoundsCheckRange32::New(
        alloc(), dstArrayIndex, numElements, dstNumElements, trapSiteDesc());
    if (!dstBoundsCheck) {
      return false;
    }
    curBlock_->add(dstBoundsCheck);

    MInstruction* srcBoundsCheck = MWasmBoundsCheckRange32::New(
        alloc(), srcArrayIndex, numElements, srcNumElements, trapSiteDesc());
    if (!srcBoundsCheck) {
      return false;
    }
    curBlock_->add(srcBoundsCheck);

    // Check if numElements != 0 -- optimization to not invoke builtins.
    MBasicBlock* copyBlock = nullptr;
    if (!newBlock(curBlock_, &copyBlock)) {
      return false;
    }
    MBasicBlock* joinBlock = nullptr;
    if (!newBlock(curBlock_, &joinBlock)) {
      return false;
    }

    MInstruction* condition =
        MCompare::NewWasm(alloc(), numElements, constantI32(0), JSOp::StrictEq,
                          MCompare::Compare_UInt32);
    curBlock_->add(condition);

    MTest* test = MTest::New(alloc(), condition, joinBlock, copyBlock);
    if (!test) {
      return false;
    }
    curBlock_->end(test);
    curBlock_ = copyBlock;

    if (elemsAreRefTyped) {
      MOZ_RELEASE_ASSERT(elemSize == sizeof(void*));

      if (!builtinCall5(SASigArrayRefsMove, bytecodeOffset, dstArrayObject,
                        dstArrayIndex, srcArrayObject, srcArrayIndex,
                        numElements, nullptr)) {
        return false;
      }
    } else {
      MDefinition* elemSizeDef = constantI32(elemSize);
      if (!elemSizeDef) {
        return false;
      }

      if (!builtinCall6(SASigArrayMemMove, bytecodeOffset, dstArrayObject,
                        dstArrayIndex, srcArrayObject, srcArrayIndex,
                        elemSizeDef, numElements, nullptr)) {
        return false;
      }
    }

    MGoto* fallthrough = MGoto::New(alloc(), joinBlock);
    if (!fallthrough) {
      return false;
    }
    curBlock_->end(fallthrough);
    if (!joinBlock->addPredecessor(alloc(), curBlock_)) {
      return false;
    }
    curBlock_ = joinBlock;
    return true;
  }

  [[nodiscard]] bool createArrayFill(uint32_t bytecodeOffset,
                                     uint32_t typeIndex,
                                     MDefinition* arrayObject,
                                     MDefinition* index, MDefinition* val,
                                     MDefinition* numElements) {
    MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);
    MOZ_ASSERT(index->type() == MIRType::Int32);
    MOZ_ASSERT(numElements->type() == MIRType::Int32);

    const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();

    // Check for null is done in getWasmArrayObjectNumElements.

    // Get the array's actual size.
    MDefinition* actualNumElements = getWasmArrayObjectNumElements(arrayObject);
    if (!actualNumElements) {
      return false;
    }

    // Create a bounds check.
    auto* boundsCheck = MWasmBoundsCheckRange32::New(
        alloc(), index, numElements, actualNumElements, trapSiteDesc());
    if (!boundsCheck) {
      return false;
    }
    curBlock_->add(boundsCheck);

    return fillArray(bytecodeOffset, arrayType, arrayObject, index, numElements,
                     val, WasmPreBarrierKind::Normal,
                     WasmPostBarrierKind::Edge);
  }

  /*********************************************** WasmGC: other helpers ***/

  // Generate MIR that attempts to cast `ref` to `castToTypeDef`.  If the
  // cast fails, we trap.  If it succeeds, then `ref` can be assumed to
  // have a type that is a subtype of (or the same as) `castToTypeDef` after
  // this point.
  [[nodiscard]] MDefinition* refCast(MDefinition* ref, RefType destType) {
    MInstruction* cast = nullptr;
    if (destType.isTypeRef()) {
      uint32_t typeIndex = codeMeta().types->indexOf(*destType.typeDef());
      MDefinition* superSTV = loadSuperTypeVector(typeIndex);
      if (!superSTV) {
        return nullptr;
      }
      cast = MWasmRefCastConcrete::New(alloc(), ref, superSTV, destType,
                                       trapSiteDesc());
    } else {
      cast = MWasmRefCastAbstract::New(alloc(), ref, destType, trapSiteDesc());
    }

    if (!cast) {
      return nullptr;
    }
    curBlock_->add(cast);
    return cast;
  }

  // Generate MIR that computes a boolean value indicating whether or not it
  // is possible to cast `ref` to `destType`.
  [[nodiscard]] MDefinition* refTest(MDefinition* ref, RefType destType) {
    MInstruction* isSubTypeOf = nullptr;
    if (destType.isTypeRef()) {
      uint32_t typeIndex = codeMeta().types->indexOf(*destType.typeDef());
      MDefinition* superSTV = loadSuperTypeVector(typeIndex);
      if (!superSTV) {
        return nullptr;
      }
      isSubTypeOf = MWasmRefTestConcrete::New(alloc(), ref, superSTV, destType);
    } else {
      isSubTypeOf = MWasmRefTestAbstract::New(alloc(), ref, destType);
    }
    MOZ_ASSERT(isSubTypeOf);

    curBlock_->add(isSubTypeOf);
    return isSubTypeOf;
  }

  // Generates MIR for br_on_cast and br_on_cast_fail.
  [[nodiscard]] bool brOnCastCommon(bool onSuccess, uint32_t labelRelativeDepth,
                                    RefType sourceType, RefType destType,
                                    const ResultType& labelType,
                                    const DefVector& values,
                                    BranchHint branchHint) {
    if (inDeadCode()) {
      return true;
    }

    MBasicBlock* fallthroughBlock = nullptr;
    if (!newBlock(curBlock_, &fallthroughBlock)) {
      return false;
    }

    // `values` are the values in the top block-value on the stack.  Since the
    // argument to `br_on_cast{_fail}` is at the top of the stack, it is the
    // last element in `values`.
    //
    // For both br_on_cast and br_on_cast_fail, the OpIter validation routines
    // ensure that `values` is non-empty (by rejecting the case
    // `labelType->length() < 1`) and that the last value in `values` is
    // reftyped.
    MOZ_RELEASE_ASSERT(values.length() > 0);
    MDefinition* ref = values.back();
    MOZ_ASSERT(ref->type() == MIRType::WasmAnyRef);

    MDefinition* success = refTest(ref, destType);
    if (!success) {
      return false;
    }

    MTest* test;
    if (onSuccess) {
      test = MTest::New(alloc(), success, nullptr, fallthroughBlock);
      if (!test || !addControlFlowPatch(test, labelRelativeDepth,
                                        MTest::TrueBranchIndex, branchHint)) {
        return false;
      }
    } else {
      test = MTest::New(alloc(), success, fallthroughBlock, nullptr);
      if (!test || !addControlFlowPatch(test, labelRelativeDepth,
                                        MTest::FalseBranchIndex, branchHint)) {
        return false;
      }
    }

    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(test);
    curBlock_ = fallthroughBlock;
    return true;
  }

  [[nodiscard]] bool brOnNonStruct(const DefVector& values) {
    if (inDeadCode()) {
      return true;
    }

    MBasicBlock* fallthroughBlock = nullptr;
    if (!newBlock(curBlock_, &fallthroughBlock)) {
      return false;
    }

    MOZ_ASSERT(values.length() > 0);
    MOZ_ASSERT(values.back()->type() == MIRType::WasmAnyRef);

    MGoto* jump = MGoto::New(alloc(), fallthroughBlock);
    if (!jump) {
      return false;
    }
    if (!pushDefs(values)) {
      return false;
    }

    curBlock_->end(jump);
    curBlock_ = fallthroughBlock;
    return true;
  }

  [[nodiscard]] MDefinition* convertAnyExtern(MDefinition* ref,
                                              wasm::RefType::Kind kind) {
    auto* converted = MWasmRefConvertAnyExtern::New(alloc(), ref, kind);
    if (!converted) {
      return nullptr;
    }
    curBlock_->add(converted);
    return converted;
  }

  /************************************************************ DECODING ***/

  uint32_t readBytecodeOffset() { return iter_.lastOpcodeOffset(); }

  CallRefHint readCallRefHint() {
    // We don't track anything if we're not using lazy tiering
    if (compilerEnv().mode() != CompileMode::LazyTiering) {
      return CallRefHint();
    }

    CallRefMetricsRange rangeInModule =
        codeTailMeta()->getFuncDefCallRefs(funcIndex());
    uint32_t localIndex = numCallRefs_++;
    MOZ_RELEASE_ASSERT(localIndex < rangeInModule.length);
    uint32_t moduleIndex = rangeInModule.begin + localIndex;
    return codeTailMeta()->getCallRefHint(moduleIndex);
  }

#if DEBUG
  bool done() const { return iter_.done(); }
#endif

  /*************************************************************************/
 private:
  [[nodiscard]] bool newBlock(MBasicBlock* pred, MBasicBlock** block,
                              MBasicBlock::Kind kind = MBasicBlock::NORMAL) {
    *block = MBasicBlock::New(mirGraph(), info(), pred, kind);
    if (!*block) {
      return false;
    }
    mirGraph().addBlock(*block);
    (*block)->setLoopDepth(rootCompiler_.loopDepth());
    return true;
  }

  [[nodiscard]] bool goToNewBlock(MBasicBlock* pred, MBasicBlock** block) {
    if (!newBlock(pred, block)) {
      return false;
    }
    pred->end(MGoto::New(alloc(), *block));
    return true;
  }

  [[nodiscard]] bool goToExistingBlock(MBasicBlock* prev, MBasicBlock* next) {
    MOZ_ASSERT(prev);
    MOZ_ASSERT(next);
    prev->end(MGoto::New(alloc(), next));
    return next->addPredecessor(alloc(), prev);
  }

  [[nodiscard]] bool bindBranches(uint32_t absolute, DefVector* defs) {
    if (absolute >= pendingBlocks_.length() ||
        pendingBlocks_[absolute].patches.empty()) {
      return inDeadCode() || popPushedDefs(defs);
    }

    ControlFlowPatchVector& patches = pendingBlocks_[absolute].patches;
    MControlInstruction* ins = patches[0].ins;
    MBasicBlock* pred = ins->block();

    MBasicBlock* join = nullptr;
    if (!newBlock(pred, &join)) {
      return false;
    }

    // Use branch hinting information if any.
    if (pendingBlocks_[absolute].hint != BranchHint::Invalid) {
      BranchHint hint = pendingBlocks_[absolute].hint;
      if (hint == BranchHint::Likely) {
        join->setFrequency(Frequency::Likely);
      } else if (hint == BranchHint::Unlikely) {
        join->setFrequency(Frequency::Unlikely);
      }
    }

    pred->mark();
    ins->replaceSuccessor(patches[0].index, join);

    for (size_t i = 1; i < patches.length(); i++) {
      ins = patches[i].ins;

      pred = ins->block();
      if (!pred->isMarked()) {
        if (!join->addPredecessor(alloc(), pred)) {
          return false;
        }
        pred->mark();
      }

      ins->replaceSuccessor(patches[i].index, join);
    }

    MOZ_ASSERT_IF(curBlock_, !curBlock_->isMarked());
    for (uint32_t i = 0; i < join->numPredecessors(); i++) {
      join->getPredecessor(i)->unmark();
    }

    if (curBlock_ && !goToExistingBlock(curBlock_, join)) {
      return false;
    }

    curBlock_ = join;

    if (!popPushedDefs(defs)) {
      return false;
    }

    patches.clear();
    return true;
  }

  bool emitI32Const();
  bool emitI64Const();
  bool emitF32Const();
  bool emitF64Const();
  bool emitBlock();
  bool emitLoop();
  bool emitIf();
  bool emitElse();
  bool emitEnd();
  bool emitBr();
  bool emitBrIf();
  bool emitBrTable();
  bool emitReturn();
  bool emitUnreachable();
  bool emitTry();
  bool emitCatch();
  bool emitCatchAll();
  bool emitTryTable();
  bool emitDelegate();
  bool emitThrow();
  bool emitThrowRef();
  bool emitRethrow();
  bool emitInlineCall(const FuncType& funcType, uint32_t funcIndex,
                      InliningHeuristics::CallKind callKind,
                      const DefVector& args, DefVector* results);
  bool emitCall();
  bool emitCallIndirect();
  bool emitReturnCall();
  bool emitReturnCallIndirect();
  bool emitReturnCallRef();
#ifdef ENABLE_WASM_JSPI
  bool emitContNew();
  bool emitContBind();
  bool emitStoreSuspendParams(MDefinition* paramsArea,
                              const ValTypeVector& suspendTagParams,
                              const DefVector& suspendParams,
                              MDefinition* suspendedCont);
  bool emitSuspend();
  bool emitResume();
  bool emitResumeThrow();
  bool emitResumeThrowRef();
  bool emitSwitch();
  bool emitGuardSuspending();
#endif
  bool emitGetLocal();
  bool emitSetLocal();
  bool emitTeeLocal();
  bool emitGetGlobal();
  bool emitSetGlobal();
  bool emitTeeGlobal();
  template <typename MIRClass>
  bool emitUnary(ValType operandType);
  template <typename MIRClass>
  bool emitConversion(ValType operandType, ValType resultType);
  template <typename MIRClass>
  bool emitUnaryWithType(ValType operandType, MIRType mirType);
  template <typename MIRClass>
  bool emitConversionWithType(ValType operandType, ValType resultType,
                              MIRType mirType);
  bool emitTruncate(ValType operandType, ValType resultType, bool isUnsigned,
                    bool isSaturating);
  bool emitSignExtend(uint32_t srcSize, uint32_t targetSize);
  bool emitExtendI32(bool isUnsigned);
  bool emitWrapI32();
  bool emitConvertI64ToFloatingPoint(ValType resultType, MIRType mirType,
                                     bool isUnsigned);
  bool emitReinterpret(ValType resultType, ValType operandType,
                       MIRType mirType);
  bool emitAdd(ValType type, MIRType mirType);
  bool emitSub(ValType type, MIRType mirType);
  bool emitRotate(ValType type, bool isLeftRotation);
  bool emitBitwiseAndOrXor(ValType operandType, MIRType mirType,
                           MWasmBinaryBitwise::SubOpcode subOpc);
  template <typename MIRClass>
  bool emitShift(ValType operandType, MIRType mirType);
  bool emitUrsh(ValType operandType, MIRType mirType);
  bool emitMul(ValType operandType, MIRType mirType);
  bool emitDiv(ValType operandType, MIRType mirType, bool isUnsigned);
  bool emitRem(ValType operandType, MIRType mirType, bool isUnsigned);
  bool emitMinMax(ValType operandType, MIRType mirType, bool isMax);
  bool emitCopySign(ValType operandType);
  bool emitComparison(ValType operandType, JSOp compareOp,
                      MCompare::CompareType compareType);
  bool emitSelect(bool typed);
  bool emitLoad(ValType type, Scalar::Type viewType);
  bool emitStore(ValType resultType, Scalar::Type viewType);
  bool tryInlineUnaryBuiltin(SymbolicAddress callee, MDefinition* input);
  bool emitUnaryMathBuiltinCall(const SymbolicAddressSignature& callee);
  bool emitMemoryGrow();
  bool emitMemorySize();
  bool emitAtomicCmpXchg(ValType type, Scalar::Type viewType);
  bool emitAtomicLoad(ValType type, Scalar::Type viewType);
  bool emitAtomicRMW(ValType type, Scalar::Type viewType, jit::AtomicOp op);
  bool emitAtomicStore(ValType type, Scalar::Type viewType);
  bool emitWait(ValType type, uint32_t byteSize);
  bool emitFence();
  bool emitNotify();
  bool emitAtomicXchg(ValType type, Scalar::Type viewType);
  bool emitMemCopyCall(uint32_t dstMemIndex, uint32_t srcMemIndex,
                       MDefinition* dst, MDefinition* src, MDefinition* len);
  bool emitMemCopyInline(uint32_t memoryIndex, MDefinition* dst,
                         MDefinition* src, uint32_t length);
  bool emitMemCopy();
  bool emitTableCopy();
  bool emitDataOrElemDrop(bool isData);
  bool emitMemFillCall(uint32_t memoryIndex, MDefinition* start,
                       MDefinition* val, MDefinition* len);
  bool emitMemFillInline(uint32_t memoryIndex, MDefinition* start,
                         MDefinition* val, uint32_t length);
  bool emitMemFill();
  bool emitMemInit();
  bool emitTableInit();
  bool emitTableFill();
  bool emitMemDiscard();
  bool emitTableGet();
  bool emitTableGrow();
  bool emitTableSet();
  bool emitTableSize();
  bool emitRefFunc();
  bool emitRefNull();
  bool emitRefIsNull();
  bool emitI64AddSub128(bool isAdd);
  bool emitI64MulWide(bool isSigned);
  bool emitConstSimd128();
  bool emitBinarySimd128(bool commutative, SimdOp op);
  bool emitTernarySimd128(wasm::SimdOp op);
  bool emitShiftSimd128(SimdOp op);
  bool emitSplatSimd128(ValType inType, SimdOp op);
  bool emitUnarySimd128(SimdOp op);
  bool emitReduceSimd128(SimdOp op);
  bool emitExtractLaneSimd128(ValType outType, uint32_t laneLimit, SimdOp op);
  bool emitReplaceLaneSimd128(ValType laneType, uint32_t laneLimit, SimdOp op);
  bool emitShuffleSimd128();
  bool emitLoadSplatSimd128(Scalar::Type viewType, wasm::SimdOp splatOp);
  bool emitLoadExtendSimd128(wasm::SimdOp op);
  bool emitLoadZeroSimd128(Scalar::Type viewType, size_t numBytes);
  bool emitLoadLaneSimd128(uint32_t laneSize);
  bool emitStoreLaneSimd128(uint32_t laneSize);
  bool emitRefAsNonNull();
  bool emitBrOnNull();
  bool emitBrOnNonNull();
  bool emitSpeculativeInlineCallRef(uint32_t bytecodeOffset,
                                    const FuncType& funcType,
                                    CallRefHint expectedFuncIndices,
                                    MDefinition* actualCalleeFunc,
                                    const DefVector& args, DefVector* results);
  bool emitCallRef();
  bool emitStructNew();
  bool emitStructNewDefault();
  bool emitStructSet();
  bool emitStructGet(FieldWideningOp wideningOp);
  bool emitArrayNew();
  bool emitArrayNewDefault();
  bool emitArrayNewFixed();
  bool emitArrayNewData();
  bool emitArrayNewElem();
  bool emitArrayInitData();
  bool emitArrayInitElem();
  bool emitArraySet();
  bool emitArrayGet(FieldWideningOp wideningOp);
  bool emitArrayLen();
  bool emitArrayCopy();
  bool emitArrayFill();
  bool emitRefI31();
  bool emitI31Get(FieldWideningOp wideningOp);
  bool emitRefTest(bool nullable);
  bool emitRefCast(bool nullable);
  bool emitBrOnCast(bool onSuccess);
  bool emitAnyConvertExtern();
  bool emitExternConvertAny();
  bool emitCallBuiltinModuleFunc();

 public:
  bool emitBodyExprs();
};

template <>
MDefinition* FunctionCompiler::unary<MToFloat32>(MDefinition* op) {
  if (inDeadCode()) {
    return nullptr;
  }
  auto* ins = MToFloat32::New(alloc(), op, mustPreserveNaN(op->type()));
  curBlock_->add(ins);
  return ins;
}

template <>
MDefinition* FunctionCompiler::unary<MNot>(MDefinition* op) {
  if (inDeadCode()) {
    return nullptr;
  }
  auto* ins = MNot::NewInt32(alloc(), op);
  curBlock_->add(ins);
  return ins;
}

template <>
MDefinition* FunctionCompiler::unary<MAbs>(MDefinition* op, MIRType type) {
  if (inDeadCode()) {
    return nullptr;
  }
  auto* ins = MAbs::NewWasm(alloc(), op, type);
  curBlock_->add(ins);
  return ins;
}

bool FunctionCompiler::emitI32Const() {
  int32_t i32;
  if (!iter().readI32Const(&i32)) {
    return false;
  }

  iter().setResult(constantI32(i32));
  return true;
}

bool FunctionCompiler::emitI64Const() {
  int64_t i64;
  if (!iter().readI64Const(&i64)) {
    return false;
  }

  iter().setResult(constantI64(i64));
  return true;
}

bool FunctionCompiler::emitF32Const() {
  float f32;
  if (!iter().readF32Const(&f32)) {
    return false;
  }

  iter().setResult(constantF32(f32));
  return true;
}

bool FunctionCompiler::emitF64Const() {
  double f64;
  if (!iter().readF64Const(&f64)) {
    return false;
  }

  iter().setResult(constantF64(f64));
  return true;
}

bool FunctionCompiler::emitBlock() {
  BlockType type;
  return iter().readBlock(&type) && startBlock();
}

bool FunctionCompiler::emitLoop() {
  BlockType type;
  if (!iter().readLoop(&type)) {
    return false;
  }

  MBasicBlock* loopHeader;
  if (!startLoop(&loopHeader, type.params().length())) {
    return false;
  }

  addInterruptCheck();

  iter().controlItem().block = loopHeader;
  return true;
}

bool FunctionCompiler::emitIf() {
  BranchHint branchHint =
      iter().getBranchHint(funcIndex(), relativeBytecodeOffset());

  BlockType type;
  MDefinition* condition = nullptr;
  if (!iter().readIf(&type, &condition)) {
    return false;
  }

  MBasicBlock* elseBlock;
  if (!branchAndStartThen(condition, &elseBlock)) {
    return false;
  }

  // Store the branch hint in the basic block.
  if (!inDeadCode() && branchHint != BranchHint::Invalid) {
    if (branchHint == BranchHint::Likely) {
      getCurBlock()->setFrequency(Frequency::Likely);
    } else if (branchHint == BranchHint::Unlikely) {
      getCurBlock()->setFrequency(Frequency::Unlikely);
    }
  }

  iter().controlItem().block = elseBlock;
  return true;
}

bool FunctionCompiler::emitElse() {
  ResultType paramType;
  ResultType resultType;
  DefVector thenValues;
  if (!iter().readElse(&paramType, &resultType, &thenValues)) {
    return false;
  }

  if (!pushDefs(thenValues)) {
    return false;
  }

  Control& control = iter().controlItem();
  return switchToElse(control.block, &control.block);
}

bool FunctionCompiler::emitEnd() {
  LabelKind kind;
  ResultType type;
  DefVector preJoinDefs;
  DefVector resultsForEmptyElse;
  if (!iter().readEnd(&kind, &type, &preJoinDefs, &resultsForEmptyElse)) {
    return false;
  }

  Control& control = iter().controlItem();
  MBasicBlock* block = control.block;

  if (!pushDefs(preJoinDefs)) {
    return false;
  }

  // Every label case is responsible to pop the control item at the appropriate
  // time for the label case
  DefVector postJoinDefs;
  switch (kind) {
    case LabelKind::Body: {
      MOZ_ASSERT(!control.tryControl);
      if (!emitBodyRethrowPad(control)) {
        return false;
      }
      if (!finishBlock(&postJoinDefs)) {
        return false;
      }
      if (!returnValues(std::move(postJoinDefs))) {
        return false;
      }
      iter().popEnd();
      MOZ_ASSERT(iter().controlStackEmpty());
      return iter().endFunction(iter().end());
    }
    case LabelKind::Block:
      MOZ_ASSERT(!control.tryControl);
      if (!finishBlock(&postJoinDefs)) {
        return false;
      }
      iter().popEnd();
      break;
    case LabelKind::Loop:
      MOZ_ASSERT(!control.tryControl);
      if (!closeLoop(block, &postJoinDefs)) {
        return false;
      }
      iter().popEnd();
      break;
    case LabelKind::Then: {
      MOZ_ASSERT(!control.tryControl);
      // If we didn't see an Else, create a trivial else block so that we create
      // a diamond anyway, to preserve Ion invariants.
      if (!switchToElse(block, &block)) {
        return false;
      }

      if (!pushDefs(resultsForEmptyElse)) {
        return false;
      }

      if (!joinIfElse(block, &postJoinDefs)) {
        return false;
      }
      iter().popEnd();
      break;
    }
    case LabelKind::Else:
      MOZ_ASSERT(!control.tryControl);
      if (!joinIfElse(block, &postJoinDefs)) {
        return false;
      }
      iter().popEnd();
      break;
    case LabelKind::Try:
    case LabelKind::Catch:
    case LabelKind::CatchAll:
      MOZ_ASSERT(control.tryControl);
      if (!finishTryCatch(kind, control, &postJoinDefs)) {
        return false;
      }
      rootCompiler().freeTryControl(std::move(control.tryControl));
      iter().popEnd();
      break;
    case LabelKind::TryTable:
      MOZ_ASSERT(control.tryControl);
      if (!finishTryTable(control, &postJoinDefs)) {
        return false;
      }
      rootCompiler().freeTryControl(std::move(control.tryControl));
      iter().popEnd();
      break;
  }

  MOZ_ASSERT_IF(!inDeadCode(), postJoinDefs.length() == type.length());
  iter().setResults(postJoinDefs.length(), postJoinDefs);

  return true;
}

bool FunctionCompiler::emitBr() {
  uint32_t relativeDepth;
  ResultType type;
  DefVector values;
  if (!iter().readBr(&relativeDepth, &type, &values)) {
    return false;
  }

  return br(relativeDepth, values);
}

bool FunctionCompiler::emitBrIf() {
  uint32_t relativeDepth;
  ResultType type;
  DefVector values;
  MDefinition* condition;

  BranchHint branchHint =
      iter().getBranchHint(funcIndex(), relativeBytecodeOffset());

  if (!iter().readBrIf(&relativeDepth, &type, &values, &condition)) {
    return false;
  }

  return brIf(relativeDepth, values, condition, branchHint);
}

bool FunctionCompiler::emitBrTable() {
  Uint32Vector depths;
  uint32_t defaultDepth;
  ResultType branchValueType;
  DefVector branchValues;
  MDefinition* index;
  if (!iter().readBrTable(&depths, &defaultDepth, &branchValueType,
                          &branchValues, &index)) {
    return false;
  }

  // If all the targets are the same, or there are no targets, we can just
  // use a goto. This is not just an optimization: MaybeFoldConditionBlock
  // assumes that tables have more than one successor.
  bool allSameDepth = true;
  for (uint32_t depth : depths) {
    if (depth != defaultDepth) {
      allSameDepth = false;
      break;
    }
  }

  if (allSameDepth) {
    return br(defaultDepth, branchValues);
  }

  return brTable(index, defaultDepth, depths, branchValues);
}

bool FunctionCompiler::emitReturn() {
  DefVector values;
  if (!iter().readReturn(&values)) {
    return false;
  }

  return returnValues(std::move(values));
}

bool FunctionCompiler::emitUnreachable() {
  if (!iter().readUnreachable()) {
    return false;
  }

  unreachableTrap();
  return true;
}

bool FunctionCompiler::emitTry() {
  BlockType type;
  if (!iter().readTry(&type)) {
    return false;
  }

  return startTry();
}

bool FunctionCompiler::emitCatch() {
  LabelKind kind;
  uint32_t tagIndex;
  ResultType paramType, resultType;
  DefVector tryValues;
  if (!iter().readCatch(&kind, &tagIndex, &paramType, &resultType,
                        &tryValues)) {
    return false;
  }

  // Pushing the results of the previous block, to properly join control flow
  // after the try and after each handler, as well as potential control flow
  // patches from other instrunctions. This is similar to what is done for
  // if-then-else control flow and for most other control control flow joins.
  if (!pushDefs(tryValues)) {
    return false;
  }

  return switchToCatch(iter().controlItem(), kind, tagIndex);
}

bool FunctionCompiler::emitCatchAll() {
  LabelKind kind;
  ResultType paramType, resultType;
  DefVector tryValues;
  if (!iter().readCatchAll(&kind, &paramType, &resultType, &tryValues)) {
    return false;
  }

  // Pushing the results of the previous block, to properly join control flow
  // after the try and after each handler, as well as potential control flow
  // patches from other instrunctions.
  if (!pushDefs(tryValues)) {
    return false;
  }

  return switchToCatch(iter().controlItem(), kind, CatchAllIndex);
}

bool FunctionCompiler::emitTryTable() {
  BlockType type;
  TryTableCatchVector catches;
  if (!iter().readTryTable(&type, &catches)) {
    return false;
  }

  return startTryTable(std::move(catches));
}

bool FunctionCompiler::emitDelegate() {
  uint32_t relativeDepth;
  ResultType resultType;
  DefVector tryValues;
  if (!iter().readDelegate(&relativeDepth, &resultType, &tryValues)) {
    return false;
  }

  Control& control = iter().controlItem();
  MBasicBlock* block = control.block;
  MOZ_ASSERT(control.tryControl);

  // Unless the entire try-delegate is dead code, delegate any pad-patches from
  // this try to the next try-block above relativeDepth.
  if (block) {
    ControlInstructionVector& padPatches =
        control.tryControl->landingPadPatches;
    if (!delegatePadPatches(padPatches, relativeDepth)) {
      return false;
    }
  }
  rootCompiler().freeTryControl(std::move(control.tryControl));
  iter().popDelegate();

  // Push the results of the previous block, and join control flow with
  // potential control flow patches from other instrunctions in the try code.
  // This is similar to what is done for EmitEnd.
  if (!pushDefs(tryValues)) {
    return false;
  }
  DefVector postJoinDefs;
  if (!finishBlock(&postJoinDefs)) {
    return false;
  }
  MOZ_ASSERT_IF(!inDeadCode(), postJoinDefs.length() == resultType.length());
  iter().setResults(postJoinDefs.length(), postJoinDefs);

  return true;
}

bool FunctionCompiler::emitThrow() {
  uint32_t tagIndex;
  DefVector argValues;
  if (!iter().readThrow(&tagIndex, &argValues)) {
    return false;
  }

  return emitThrow(tagIndex, argValues);
}

bool FunctionCompiler::emitThrowRef() {
  MDefinition* exnRef;
  if (!iter().readThrowRef(&exnRef)) {
    return false;
  }

  return emitThrowRef(exnRef);
}

bool FunctionCompiler::emitRethrow() {
  uint32_t relativeDepth;
  if (!iter().readRethrow(&relativeDepth)) {
    return false;
  }

  return emitRethrow(relativeDepth);
}

bool FunctionCompiler::emitInlineCall(const FuncType& funcType,
                                      uint32_t funcIndex,
                                      InliningHeuristics::CallKind callKind,
                                      const DefVector& args,
                                      DefVector* results) {
  UniqueChars error;
  const BytecodeRange& funcRange = codeTailMeta()->funcDefRange(funcIndex);
  BytecodeSpan funcBytecode = codeTailMeta()->funcDefBody(funcIndex);
  FuncCompileInput func(funcIndex, funcRange.start, funcBytecode.data(),
                        funcBytecode.data() + funcBytecode.size());
  Decoder d(func.begin, func.end, func.bytecodeOffset, &error);

  ValTypeVector locals;
  if (!DecodeLocalEntriesWithParams(d, codeMeta(), funcIndex, &locals)) {
    return false;
  }

  CompileInfo* compileInfo = rootCompiler().startInlineCall(
      this->funcIndex(), bytecodeOffset(), funcIndex, locals.length(),
      funcRange.size(), callKind);
  if (!compileInfo) {
    return false;
  }

  FunctionCompiler calleeCompiler(this, d, func, locals, *compileInfo);
  if (!calleeCompiler.initInline(args)) {
    MOZ_ASSERT(!error);
    return false;
  }

  if (!calleeCompiler.startBlock()) {
    MOZ_ASSERT(!error);
    return false;
  }

  if (!calleeCompiler.emitBodyExprs()) {
    MOZ_ASSERT(!error);
    return false;
  }

  calleeCompiler.finish();
  rootCompiler_.finishInlineCall();

  return finishInlinedCallDirect(calleeCompiler, results);
}

bool FunctionCompiler::emitCall() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t funcIndex;
  DefVector args;
  if (!iter().readCall(&funcIndex, &args)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  const FuncType& funcType = codeMeta().getFuncType(funcIndex);

  DefVector results;
  if (codeMeta().funcIsImport(funcIndex)) {
    BuiltinModuleFuncId knownFuncImport = codeMeta().knownFuncImport(funcIndex);
    if (knownFuncImport != BuiltinModuleFuncId::None) {
      const BuiltinModuleFunc& builtinModuleFunc =
          BuiltinModuleFuncs::getFromId(knownFuncImport);
      return callBuiltinModuleFunc(builtinModuleFunc, args);
    }

    uint32_t instanceDataOffset =
        codeMeta().offsetOfFuncImportInstanceData(funcIndex);
    if (!callImport(instanceDataOffset, bytecodeOffset, funcType, args,
                    &results)) {
      return false;
    }
  } else {
    const auto callKind = InliningHeuristics::CallKind::Direct;
    // Make up a single-entry CallRefHint and enquire about its inlineability.
    CallRefHint hints;
    hints.append(funcIndex);
    hints = auditInlineableCallees(callKind, hints);
    if (!hints.empty()) {
      // Inlining of `funcIndex` was approved.
      if (!emitInlineCall(funcType, funcIndex, callKind, args, &results)) {
        return false;
      }
    } else {
      if (!callDirect(funcType, funcIndex, bytecodeOffset, args, &results)) {
        return false;
      }
    }
  }

  iter().setResults(results.length(), results);
  return true;
}

bool FunctionCompiler::emitCallIndirect() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t funcTypeIndex;
  uint32_t tableIndex;
  MDefinition* callee;
  DefVector args;
  if (!iter().readCallIndirect(&funcTypeIndex, &tableIndex, &callee, &args)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  DefVector results;
  if (!callIndirect(funcTypeIndex, tableIndex, callee, bytecodeOffset, args,
                    &results)) {
    return false;
  }

  iter().setResults(results.length(), results);
  return true;
}

bool FunctionCompiler::emitReturnCall() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t funcIndex;
  DefVector args;
  if (!iter().readReturnCall(&funcIndex, &args)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  const FuncType& funcType = codeMeta().getFuncType(funcIndex);

  DefVector results;
  if (codeMeta().funcIsImport(funcIndex)) {
    uint32_t globalDataOffset =
        codeMeta().offsetOfFuncImportInstanceData(funcIndex);
    if (!returnCallImport(globalDataOffset, bytecodeOffset, funcType, args,
                          &results)) {
      return false;
    }
  } else {
    if (!returnCallDirect(funcType, funcIndex, bytecodeOffset, args,
                          &results)) {
      return false;
    }
  }
  return true;
}

bool FunctionCompiler::emitReturnCallIndirect() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t funcTypeIndex;
  uint32_t tableIndex;
  MDefinition* callee;
  DefVector args;
  if (!iter().readReturnCallIndirect(&funcTypeIndex, &tableIndex, &callee,
                                     &args)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  DefVector results;
  return returnCallIndirect(funcTypeIndex, tableIndex, callee, bytecodeOffset,
                            args, &results);
}

bool FunctionCompiler::emitReturnCallRef() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t funcTypeIndex;
  MDefinition* callee;
  DefVector args;

  if (!iter().readReturnCallRef(&funcTypeIndex, &callee, &args)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  const FuncType& funcType = codeMeta().types->type(funcTypeIndex).funcType();
  DefVector results;
  return returnCallRef(funcType, callee, bytecodeOffset, args, &results);
}

bool FunctionCompiler::emitGetLocal() {
  uint32_t id;
  if (!iter().readGetLocal(&id)) {
    return false;
  }

  iter().setResult(getLocalDef(id));
  return true;
}

bool FunctionCompiler::emitSetLocal() {
  uint32_t id;
  MDefinition* value;
  if (!iter().readSetLocal(&id, &value)) {
    return false;
  }

  assign(id, value);
  return true;
}

bool FunctionCompiler::emitTeeLocal() {
  uint32_t id;
  MDefinition* value;
  if (!iter().readTeeLocal(&id, &value)) {
    return false;
  }

  assign(id, value);
  return true;
}

bool FunctionCompiler::emitGetGlobal() {
  uint32_t id;
  if (!iter().readGetGlobal(&id)) {
    return false;
  }

  const GlobalDesc& global = codeMeta().globals[id];
  if (!global.isConstant()) {
    iter().setResult(loadGlobalVar(global));
    return true;
  }

  LitVal value = global.constantValue();

  MDefinition* result;
  switch (value.type().kind()) {
    case ValType::I32:
      result = constantI32(int32_t(value.i32()));
      break;
    case ValType::I64:
      result = constantI64(int64_t(value.i64()));
      break;
    case ValType::F32:
      result = constantF32(value.f32());
      break;
    case ValType::F64:
      result = constantF64(value.f64());
      break;
    case ValType::V128:
#ifdef ENABLE_WASM_SIMD
      result = constantV128(value.v128());
      break;
#else
      return iter().fail("Ion has no SIMD support yet");
#endif
    case ValType::Ref:
      MOZ_ASSERT(value.ref().isNull());
      result = constantNullRef(MaybeRefType(value.type().refType()));
      break;
    default:
      MOZ_CRASH("unexpected type in EmitGetGlobal");
  }

  iter().setResult(result);
  return true;
}

bool FunctionCompiler::emitSetGlobal() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t id;
  MDefinition* value;
  if (!iter().readSetGlobal(&id, &value)) {
    return false;
  }

  const GlobalDesc& global = codeMeta().globals[id];
  MOZ_ASSERT(global.isMutable());
  return storeGlobalVar(bytecodeOffset, global, value);
}

template <typename MIRClass>
bool FunctionCompiler::emitUnary(ValType operandType) {
  MDefinition* input;
  if (!iter().readUnary(operandType, &input)) {
    return false;
  }

  iter().setResult(unary<MIRClass>(input));
  return true;
}

template <typename MIRClass>
bool FunctionCompiler::emitConversion(ValType operandType, ValType resultType) {
  MDefinition* input;
  if (!iter().readConversion(operandType, resultType, &input)) {
    return false;
  }

  iter().setResult(unary<MIRClass>(input));
  return true;
}

template <typename MIRClass>
bool FunctionCompiler::emitUnaryWithType(ValType operandType, MIRType mirType) {
  MDefinition* input;
  if (!iter().readUnary(operandType, &input)) {
    return false;
  }

  iter().setResult(unary<MIRClass>(input, mirType));
  return true;
}

template <typename MIRClass>
bool FunctionCompiler::emitConversionWithType(ValType operandType,
                                              ValType resultType,
                                              MIRType mirType) {
  MDefinition* input;
  if (!iter().readConversion(operandType, resultType, &input)) {
    return false;
  }

  iter().setResult(unary<MIRClass>(input, mirType));
  return true;
}

bool FunctionCompiler::emitTruncate(ValType operandType, ValType resultType,
                                    bool isUnsigned, bool isSaturating) {
  MDefinition* input = nullptr;
  if (!iter().readConversion(operandType, resultType, &input)) {
    return false;
  }

  TruncFlags flags = 0;
  if (isUnsigned) {
    flags |= TRUNC_UNSIGNED;
  }
  if (isSaturating) {
    flags |= TRUNC_SATURATING;
  }
  if (resultType == ValType::I32) {
    iter().setResult(truncate<MWasmTruncateToInt32>(input, flags));
  } else {
    MOZ_ASSERT(resultType == ValType::I64);
#if defined(JS_CODEGEN_ARM)
    iter().setResult(truncateWithInstance(input, flags));
#else
    iter().setResult(truncate<MWasmTruncateToInt64>(input, flags));
#endif
  }
  return true;
}

bool FunctionCompiler::emitSignExtend(uint32_t srcSize, uint32_t targetSize) {
  MDefinition* input;
  ValType type = targetSize == 4 ? ValType::I32 : ValType::I64;
  if (!iter().readConversion(type, type, &input)) {
    return false;
  }

  iter().setResult(signExtend(input, srcSize, targetSize));
  return true;
}

bool FunctionCompiler::emitExtendI32(bool isUnsigned) {
  MDefinition* input;
  if (!iter().readConversion(ValType::I32, ValType::I64, &input)) {
    return false;
  }

  iter().setResult(extendI32(input, isUnsigned));
  return true;
}

bool FunctionCompiler::emitWrapI32() {
  MDefinition* input;
  if (!iter().readConversion(ValType::I64, ValType::I32, &input)) {
    return false;
  }

  iter().setResult(wrapI32(input));
  return true;
}

bool FunctionCompiler::emitConvertI64ToFloatingPoint(ValType resultType,
                                                     MIRType mirType,
                                                     bool isUnsigned) {
  MDefinition* input;
  if (!iter().readConversion(ValType::I64, resultType, &input)) {
    return false;
  }

  iter().setResult(convertI64ToFloatingPoint(input, mirType, isUnsigned));
  return true;
}

bool FunctionCompiler::emitReinterpret(ValType resultType, ValType operandType,
                                       MIRType mirType) {
  MDefinition* input;
  if (!iter().readConversion(operandType, resultType, &input)) {
    return false;
  }

  iter().setResult(unary<MReinterpretCast>(input, mirType));
  return true;
}

bool FunctionCompiler::emitAdd(ValType type, MIRType mirType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(type, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(add(lhs, rhs, mirType));
  return true;
}

bool FunctionCompiler::emitSub(ValType type, MIRType mirType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(type, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(sub(lhs, rhs, mirType));
  return true;
}

bool FunctionCompiler::emitRotate(ValType type, bool isLeftRotation) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(type, &lhs, &rhs)) {
    return false;
  }

  MDefinition* result = rotate(lhs, rhs, type.toMIRType(), isLeftRotation);
  iter().setResult(result);
  return true;
}

bool FunctionCompiler::emitBitwiseAndOrXor(
    ValType operandType, MIRType mirType,
    MWasmBinaryBitwise::SubOpcode subOpc) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(binary<MWasmBinaryBitwise>(lhs, rhs, mirType, subOpc));
  return true;
}

template <typename MIRClass>
bool FunctionCompiler::emitShift(ValType operandType, MIRType mirType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(binary<MIRClass>(lhs, rhs, mirType));
  return true;
}

bool FunctionCompiler::emitUrsh(ValType operandType, MIRType mirType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(ursh(lhs, rhs, mirType));
  return true;
}

bool FunctionCompiler::emitMul(ValType operandType, MIRType mirType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(
      mul(lhs, rhs, mirType,
          mirType == MIRType::Int32 ? MMul::Integer : MMul::Normal));
  return true;
}

bool FunctionCompiler::emitDiv(ValType operandType, MIRType mirType,
                               bool isUnsigned) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(div(lhs, rhs, mirType, isUnsigned));
  return true;
}

bool FunctionCompiler::emitRem(ValType operandType, MIRType mirType,
                               bool isUnsigned) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(mod(lhs, rhs, mirType, isUnsigned));
  return true;
}

bool FunctionCompiler::emitMinMax(ValType operandType, MIRType mirType,
                                  bool isMax) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(minMax(lhs, rhs, mirType, isMax));
  return true;
}

bool FunctionCompiler::emitCopySign(ValType operandType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(binary<MCopySign>(lhs, rhs, operandType.toMIRType()));
  return true;
}

bool FunctionCompiler::emitComparison(ValType operandType, JSOp compareOp,
                                      MCompare::CompareType compareType) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readComparison(operandType, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(compare(lhs, rhs, compareOp, compareType));
  return true;
}

bool FunctionCompiler::emitSelect(bool typed) {
  StackType type;
  MDefinition* trueValue;
  MDefinition* falseValue;
  MDefinition* condition;
  if (!iter().readSelect(typed, &type, &trueValue, &falseValue, &condition)) {
    return false;
  }

  iter().setResult(select(trueValue, falseValue, condition));
  return true;
}

bool FunctionCompiler::emitLoad(ValType type, Scalar::Type viewType) {
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readLoad(type, Scalar::byteSize(viewType), &addr)) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex));
  auto* ins = load(addr.base, &access, type);
  if (!inDeadCode() && !ins) {
    return false;
  }

  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitStore(ValType resultType, Scalar::Type viewType) {
  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* value;
  if (!iter().readStore(resultType, Scalar::byteSize(viewType), &addr,
                        &value)) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex));

  store(addr.base, &access, value);
  return true;
}

bool FunctionCompiler::tryInlineUnaryBuiltin(SymbolicAddress callee,
                                             MDefinition* input) {
  if (!input) {
    return false;
  }

  MOZ_ASSERT(IsFloatingPointType(input->type()));

  RoundingMode mode;
  if (!IsRoundingFunction(callee, &mode)) {
    return false;
  }

  if (!MNearbyInt::HasAssemblerSupport(mode)) {
    return false;
  }

  iter().setResult(nearbyInt(input, mode));
  return true;
}

bool FunctionCompiler::emitUnaryMathBuiltinCall(
    const SymbolicAddressSignature& callee) {
  MOZ_ASSERT(callee.numArgs == 1);

  uint32_t bytecodeOffset = readBytecodeOffset();

  MDefinition* input;
  if (!iter().readUnary(ValType::fromMIRType(callee.argTypes[0]), &input)) {
    return false;
  }

  if (tryInlineUnaryBuiltin(callee.identity, input)) {
    return true;
  }

  MDefinition* def;
  if (!builtinCall1(callee, bytecodeOffset, input, &def)) {
    return false;
  }

  iter().setResult(def);
  return true;
}

bool FunctionCompiler::emitMemoryGrow() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  MDefinition* delta;
  uint32_t memoryIndex;
  if (!iter().readMemoryGrow(&memoryIndex, &delta)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* memoryIndexValue = constantI32(int32_t(memoryIndex));
  if (!memoryIndexValue) {
    return false;
  }

  const SymbolicAddressSignature& callee =
      isMem32(memoryIndex) ? SASigMemoryGrowM32 : SASigMemoryGrowM64;

  MDefinition* ret;
  if (!emitInstanceCall2(bytecodeOffset, callee, delta, memoryIndexValue,
                         &ret)) {
    return false;
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitMemorySize() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t memoryIndex;
  if (!iter().readMemorySize(&memoryIndex)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* memoryIndexValue = constantI32(int32_t(memoryIndex));
  if (!memoryIndexValue) {
    return false;
  }

  const SymbolicAddressSignature& callee =
      isMem32(memoryIndex) ? SASigMemorySizeM32 : SASigMemorySizeM64;

  MDefinition* ret;
  if (!emitInstanceCall1(bytecodeOffset, callee, memoryIndexValue, &ret)) {
    return false;
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitAtomicCmpXchg(ValType type, Scalar::Type viewType) {
  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* oldValue;
  MDefinition* newValue;
  if (!iter().readAtomicCmpXchg(&addr, type, byteSize(viewType), &oldValue,
                                &newValue)) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex),
                          Synchronization::Full());
  auto* ins =
      atomicCompareExchangeHeap(addr.base, &access, type, oldValue, newValue);
  if (!inDeadCode() && !ins) {
    return false;
  }

  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitAtomicLoad(ValType type, Scalar::Type viewType) {
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readAtomicLoad(&addr, type, byteSize(viewType))) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex),
                          Synchronization::Load());
  auto* ins = load(addr.base, &access, type);
  if (!inDeadCode() && !ins) {
    return false;
  }

  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitAtomicRMW(ValType type, Scalar::Type viewType,
                                     jit::AtomicOp op) {
  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* value;
  if (!iter().readAtomicRMW(&addr, type, byteSize(viewType), &value)) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex),
                          Synchronization::Full());
  auto* ins = atomicBinopHeap(op, addr.base, &access, type, value);
  if (!inDeadCode() && !ins) {
    return false;
  }

  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitAtomicStore(ValType type, Scalar::Type viewType) {
  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* value;
  if (!iter().readAtomicStore(&addr, type, byteSize(viewType), &value)) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex),
                          Synchronization::Store());
  store(addr.base, &access, value);
  return true;
}

bool FunctionCompiler::emitWait(ValType type, uint32_t byteSize) {
  MOZ_ASSERT(type == ValType::I32 || type == ValType::I64);
  MOZ_ASSERT(type.size() == byteSize);

  uint32_t bytecodeOffset = readBytecodeOffset();

  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* expected;
  MDefinition* timeout;
  if (!iter().readWait(&addr, type, byteSize, &expected, &timeout)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MemoryAccessDesc access(addr.memoryIndex,
                          type == ValType::I32 ? Scalar::Int32 : Scalar::Int64,
                          addr.align, addr.offset, trapSiteDesc(),
                          hugeMemoryEnabled(addr.memoryIndex));
  MDefinition* ptr = computeEffectiveAddress(addr.base, &access);
  if (!ptr) {
    return false;
  }

  MDefinition* memoryIndex = constantI32(int32_t(addr.memoryIndex));
  if (!memoryIndex) {
    return false;
  }

  const SymbolicAddressSignature& callee =
      isMem32(addr.memoryIndex)
          ? (type == ValType::I32 ? SASigWaitI32M32 : SASigWaitI64M32)
          : (type == ValType::I32 ? SASigWaitI32M64 : SASigWaitI64M64);

  MDefinition* ret;
  if (!emitInstanceCall4(bytecodeOffset, callee, ptr, expected, timeout,
                         memoryIndex, &ret)) {
    return false;
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitFence() {
  if (!iter().readFence()) {
    return false;
  }

  fence();
  return true;
}

bool FunctionCompiler::emitNotify() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* count;
  if (!iter().readNotify(&addr, &count)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MemoryAccessDesc access(addr.memoryIndex, Scalar::Int32, addr.align,
                          addr.offset, trapSiteDesc(),
                          hugeMemoryEnabled(addr.memoryIndex));
  MDefinition* ptr = computeEffectiveAddress(addr.base, &access);
  if (!ptr) {
    return false;
  }

  MDefinition* memoryIndex = constantI32(int32_t(addr.memoryIndex));
  if (!memoryIndex) {
    return false;
  }

  const SymbolicAddressSignature& callee =
      isMem32(addr.memoryIndex) ? SASigWakeM32 : SASigWakeM64;

  MDefinition* ret;
  if (!emitInstanceCall3(bytecodeOffset, callee, ptr, count, memoryIndex,
                         &ret)) {
    return false;
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitAtomicXchg(ValType type, Scalar::Type viewType) {
  LinearMemoryAddress<MDefinition*> addr;
  MDefinition* value;
  if (!iter().readAtomicRMW(&addr, type, byteSize(viewType), &value)) {
    return false;
  }

  MemoryAccessDesc access(addr.memoryIndex, viewType, addr.align, addr.offset,
                          trapSiteDesc(), hugeMemoryEnabled(addr.memoryIndex),
                          Synchronization::Full());
  MDefinition* ins = atomicExchangeHeap(addr.base, &access, type, value);
  if (!inDeadCode() && !ins) {
    return false;
  }

  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitMemCopyCall(uint32_t dstMemIndex,
                                       uint32_t srcMemIndex, MDefinition* dst,
                                       MDefinition* src, MDefinition* len) {
  uint32_t bytecodeOffset = readBytecodeOffset();

  if (dstMemIndex == srcMemIndex) {
    const SymbolicAddressSignature& callee =
        (codeMeta().usesSharedMemory(dstMemIndex)
             ? (isMem32(dstMemIndex) ? SASigMemCopySharedM32
                                     : SASigMemCopySharedM64)
             : (isMem32(dstMemIndex) ? SASigMemCopyM32 : SASigMemCopyM64));
    MDefinition* base = memoryBase(dstMemIndex);
    if (!base) {
      return false;
    }
    return emitInstanceCall4(bytecodeOffset, callee, dst, src, len, base);
  }

  AddressType dstIndexType = codeMeta().memories[dstMemIndex].addressType();
  AddressType srcIndexType = codeMeta().memories[srcMemIndex].addressType();

  if (dstIndexType == AddressType::I32) {
    dst = extendI32(dst, /*isUnsigned=*/true);
    if (!dst) {
      return false;
    }
  }
  if (srcIndexType == AddressType::I32) {
    src = extendI32(src, /*isUnsigned=*/true);
    if (!src) {
      return false;
    }
  }
  if (dstIndexType == AddressType::I32 || srcIndexType == AddressType::I32) {
    len = extendI32(len, /*isUnsigned=*/true);
    if (!len) {
      return false;
    }
  }

  MDefinition* dstMemIndexValue = constantI32(int32_t(dstMemIndex));
  if (!dstMemIndexValue) {
    return false;
  }

  MDefinition* srcMemIndexValue = constantI32(int32_t(srcMemIndex));
  if (!srcMemIndexValue) {
    return false;
  }

  return emitInstanceCall5(bytecodeOffset, SASigMemCopyAny, dst, src, len,
                           dstMemIndexValue, srcMemIndexValue);
}

bool FunctionCompiler::emitMemCopyInline(uint32_t memoryIndex, MDefinition* dst,
                                         MDefinition* src, uint32_t length) {
  MOZ_ASSERT(length != 0 && length <= MaxInlineMemoryCopyLength);

  // Compute the number of copies of each width we will need to do
  size_t remainder = length;
#ifdef ENABLE_WASM_SIMD
  size_t numCopies16 = 0;
  if (MacroAssembler::SupportsFastUnalignedFPAccesses()) {
    numCopies16 = remainder / sizeof(V128);
    remainder %= sizeof(V128);
  }
#endif
#ifdef JS_64BIT
  size_t numCopies8 = remainder / sizeof(uint64_t);
  remainder %= sizeof(uint64_t);
#endif
  size_t numCopies4 = remainder / sizeof(uint32_t);
  remainder %= sizeof(uint32_t);
  size_t numCopies2 = remainder / sizeof(uint16_t);
  remainder %= sizeof(uint16_t);
  size_t numCopies1 = remainder;

  // Load all source bytes from low to high using the widest transfer width we
  // can for the system. We will trap without writing anything if any source
  // byte is out-of-bounds.
  size_t offset = 0;
  DefVector loadedValues;

#ifdef ENABLE_WASM_SIMD
  for (uint32_t i = 0; i < numCopies16; i++) {
    MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* loadValue = load(src, &access, ValType::V128);
    if (!loadValue || !loadedValues.append(loadValue)) {
      return false;
    }

    offset += sizeof(V128);
  }
#endif

#ifdef JS_64BIT
  for (uint32_t i = 0; i < numCopies8; i++) {
    MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* loadValue = load(src, &access, ValType::I64);
    if (!loadValue || !loadedValues.append(loadValue)) {
      return false;
    }

    offset += sizeof(uint64_t);
  }
#endif

  for (uint32_t i = 0; i < numCopies4; i++) {
    MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* loadValue = load(src, &access, ValType::I32);
    if (!loadValue || !loadedValues.append(loadValue)) {
      return false;
    }

    offset += sizeof(uint32_t);
  }

  if (numCopies2) {
    MemoryAccessDesc access(memoryIndex, Scalar::Uint16, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* loadValue = load(src, &access, ValType::I32);
    if (!loadValue || !loadedValues.append(loadValue)) {
      return false;
    }

    offset += sizeof(uint16_t);
  }

  if (numCopies1) {
    MemoryAccessDesc access(memoryIndex, Scalar::Uint8, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* loadValue = load(src, &access, ValType::I32);
    if (!loadValue || !loadedValues.append(loadValue)) {
      return false;
    }
  }

  // Store all source bytes to the destination from high to low. We will trap
  // without writing anything on the first store if any dest byte is
  // out-of-bounds.
  offset = length;

  if (numCopies1) {
    offset -= sizeof(uint8_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Uint8, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* value = loadedValues.popCopy();
    store(dst, &access, value);
  }

  if (numCopies2) {
    offset -= sizeof(uint16_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Uint16, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* value = loadedValues.popCopy();
    store(dst, &access, value);
  }

  for (uint32_t i = 0; i < numCopies4; i++) {
    offset -= sizeof(uint32_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* value = loadedValues.popCopy();
    store(dst, &access, value);
  }

#ifdef JS_64BIT
  for (uint32_t i = 0; i < numCopies8; i++) {
    offset -= sizeof(uint64_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* value = loadedValues.popCopy();
    store(dst, &access, value);
  }
#endif

#ifdef ENABLE_WASM_SIMD
  for (uint32_t i = 0; i < numCopies16; i++) {
    offset -= sizeof(V128);

    MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    auto* value = loadedValues.popCopy();
    store(dst, &access, value);
  }
#endif

  return true;
}

bool FunctionCompiler::emitMemCopy() {
  MDefinition *dst, *src, *len;
  uint32_t dstMemIndex;
  uint32_t srcMemIndex;
  if (!iter().readMemOrTableCopy(true, &dstMemIndex, &dst, &srcMemIndex, &src,
                                 &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  if (dstMemIndex == srcMemIndex && len->isConstant()) {
    uint64_t length = isMem32(dstMemIndex) ? len->toConstant()->toInt32()
                                           : len->toConstant()->toInt64();
    static_assert(MaxInlineMemoryCopyLength <= UINT32_MAX);
    if (length != 0 && length <= MaxInlineMemoryCopyLength) {
      return emitMemCopyInline(dstMemIndex, dst, src, uint32_t(length));
    }
  }

  return emitMemCopyCall(dstMemIndex, srcMemIndex, dst, src, len);
}

bool FunctionCompiler::emitTableCopy() {
  MDefinition *dst, *src, *len;
  uint32_t dstTableIndex;
  uint32_t srcTableIndex;
  if (!iter().readMemOrTableCopy(false, &dstTableIndex, &dst, &srcTableIndex,
                                 &src, &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();
  const TableDesc& dstTable = codeMeta().tables[dstTableIndex];
  const TableDesc& srcTable = codeMeta().tables[srcTableIndex];

  AddressType dstAddressType = dstTable.addressType();
  AddressType srcAddressType = srcTable.addressType();
  AddressType lenAddressType =
      dstAddressType == AddressType::I64 && srcAddressType == AddressType::I64
          ? AddressType::I64
          : AddressType::I32;

  MDefinition* dst32 = clampTableAddressToI32(dstAddressType, dst);
  if (!dst32) {
    return false;
  }

  MDefinition* src32 = clampTableAddressToI32(srcAddressType, src);
  if (!src32) {
    return false;
  }

  MDefinition* len32 = clampTableAddressToI32(lenAddressType, len);
  if (!len32) {
    return false;
  }

  MDefinition* dti = constantI32(int32_t(dstTableIndex));
  MDefinition* sti = constantI32(int32_t(srcTableIndex));

  return emitInstanceCall5(bytecodeOffset, SASigTableCopy, dst32, src32, len32,
                           dti, sti);
}

bool FunctionCompiler::emitDataOrElemDrop(bool isData) {
  uint32_t segIndexVal = 0;
  if (!iter().readDataOrElemDrop(isData, &segIndexVal)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();

  MDefinition* segIndex = constantI32(int32_t(segIndexVal));

  const SymbolicAddressSignature& callee =
      isData ? SASigDataDrop : SASigElemDrop;
  return emitInstanceCall1(bytecodeOffset, callee, segIndex);
}

bool FunctionCompiler::emitMemFillCall(uint32_t memoryIndex, MDefinition* start,
                                       MDefinition* val, MDefinition* len) {
  MDefinition* base = memoryBase(memoryIndex);

  uint32_t bytecodeOffset = readBytecodeOffset();
  const SymbolicAddressSignature& callee =
      (codeMeta().usesSharedMemory(memoryIndex)
           ? (isMem32(memoryIndex) ? SASigMemFillSharedM32
                                   : SASigMemFillSharedM64)
           : (isMem32(memoryIndex) ? SASigMemFillM32 : SASigMemFillM64));
  return emitInstanceCall4(bytecodeOffset, callee, start, val, len, base);
}

bool FunctionCompiler::emitMemFillInline(uint32_t memoryIndex,
                                         MDefinition* start, MDefinition* val,
                                         uint32_t length) {
  MOZ_ASSERT(length != 0 && length <= MaxInlineMemoryFillLength);
  uint32_t value = val->toConstant()->toInt32();

  // Compute the number of copies of each width we will need to do
  size_t remainder = length;
#ifdef ENABLE_WASM_SIMD
  size_t numCopies16 = 0;
  if (MacroAssembler::SupportsFastUnalignedFPAccesses()) {
    numCopies16 = remainder / sizeof(V128);
    remainder %= sizeof(V128);
  }
#endif
#ifdef JS_64BIT
  size_t numCopies8 = remainder / sizeof(uint64_t);
  remainder %= sizeof(uint64_t);
#endif
  size_t numCopies4 = remainder / sizeof(uint32_t);
  remainder %= sizeof(uint32_t);
  size_t numCopies2 = remainder / sizeof(uint16_t);
  remainder %= sizeof(uint16_t);
  size_t numCopies1 = remainder;

  // Generate splatted definitions for wider fills as needed
#ifdef ENABLE_WASM_SIMD
  MDefinition* val16 = numCopies16 ? constantV128(V128(value)) : nullptr;
#endif
#ifdef JS_64BIT
  MDefinition* val8 =
      numCopies8 ? constantI64(int64_t(SplatByteToUInt<uint64_t>(value, 8)))
                 : nullptr;
#endif
  MDefinition* val4 =
      numCopies4 ? constantI32(int32_t(SplatByteToUInt<uint32_t>(value, 4)))
                 : nullptr;
  MDefinition* val2 =
      numCopies2 ? constantI32(int32_t(SplatByteToUInt<uint32_t>(value, 2)))
                 : nullptr;

  // Store the fill value to the destination from high to low. We will trap
  // without writing anything on the first store if any dest byte is
  // out-of-bounds.
  size_t offset = length;

  if (numCopies1) {
    offset -= sizeof(uint8_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Uint8, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    store(start, &access, val);
  }

  if (numCopies2) {
    offset -= sizeof(uint16_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Uint16, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    store(start, &access, val2);
  }

  for (uint32_t i = 0; i < numCopies4; i++) {
    offset -= sizeof(uint32_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    store(start, &access, val4);
  }

#ifdef JS_64BIT
  for (uint32_t i = 0; i < numCopies8; i++) {
    offset -= sizeof(uint64_t);

    MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    store(start, &access, val8);
  }
#endif

#ifdef ENABLE_WASM_SIMD
  for (uint32_t i = 0; i < numCopies16; i++) {
    offset -= sizeof(V128);

    MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
                            trapSiteDesc(), hugeMemoryEnabled(memoryIndex));
    store(start, &access, val16);
  }
#endif

  return true;
}

bool FunctionCompiler::emitMemFill() {
  uint32_t memoryIndex;
  MDefinition *start, *val, *len;
  if (!iter().readMemFill(&memoryIndex, &start, &val, &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  if (len->isConstant() && val->isConstant()) {
    uint64_t length = isMem32(memoryIndex) ? len->toConstant()->toInt32()
                                           : len->toConstant()->toInt64();
    static_assert(MaxInlineMemoryFillLength <= UINT32_MAX);
    if (length != 0 && length <= MaxInlineMemoryFillLength) {
      return emitMemFillInline(memoryIndex, start, val, uint32_t(length));
    }
  }

  return emitMemFillCall(memoryIndex, start, val, len);
}

bool FunctionCompiler::emitMemInit() {
  uint32_t segIndexVal = 0, dstMemIndex = 0;
  MDefinition *dstOff, *srcOff, *len;
  if (!iter().readMemOrTableInit(true, &segIndexVal, &dstMemIndex, &dstOff,
                                 &srcOff, &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();
  const SymbolicAddressSignature& callee =
      (isMem32(dstMemIndex) ? SASigMemInitM32 : SASigMemInitM64);

  MDefinition* segIndex = constantI32(int32_t(segIndexVal));
  if (!segIndex) {
    return false;
  }

  MDefinition* dti = constantI32(int32_t(dstMemIndex));
  if (!dti) {
    return false;
  }

  return emitInstanceCall5(bytecodeOffset, callee, dstOff, srcOff, len,
                           segIndex, dti);
}

bool FunctionCompiler::emitTableInit() {
  uint32_t segIndexVal = 0, dstTableIndex = 0;
  MDefinition *dstOff, *srcOff, *len;
  if (!iter().readMemOrTableInit(false, &segIndexVal, &dstTableIndex, &dstOff,
                                 &srcOff, &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();
  const TableDesc& table = codeMeta().tables[dstTableIndex];

  MDefinition* dstOff32 = clampTableAddressToI32(table.addressType(), dstOff);
  if (!dstOff32) {
    return false;
  }

  MDefinition* segIndex = constantI32(int32_t(segIndexVal));
  if (!segIndex) {
    return false;
  }

  MDefinition* dti = constantI32(int32_t(dstTableIndex));
  if (!dti) {
    return false;
  }

  return emitInstanceCall5(bytecodeOffset, SASigTableInit, dstOff32, srcOff,
                           len, segIndex, dti);
}

bool FunctionCompiler::emitTableFill() {
  uint32_t tableIndex;
  MDefinition *start, *val, *len;
  if (!iter().readTableFill(&tableIndex, &start, &val, &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();
  const TableDesc& table = codeMeta().tables[tableIndex];

  MDefinition* start32 = clampTableAddressToI32(table.addressType(), start);
  if (!start32) {
    return false;
  }

  MDefinition* len32 = clampTableAddressToI32(table.addressType(), len);
  if (!len32) {
    return false;
  }

  MDefinition* tableIndexArg = constantI32(int32_t(tableIndex));
  if (!tableIndexArg) {
    return false;
  }

  return emitInstanceCall4(bytecodeOffset, SASigTableFill, start32, val, len32,
                           tableIndexArg);
}

#if ENABLE_WASM_MEMORY_CONTROL
bool FunctionCompiler::emitMemDiscard() {
  uint32_t memoryIndex;
  MDefinition *start, *len;
  if (!iter().readMemDiscard(&memoryIndex, &start, &len)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();

  MDefinition* base = memoryBase(memoryIndex);
  bool mem32 = isMem32(memoryIndex);

  const SymbolicAddressSignature& callee =
      (codeMeta().usesSharedMemory(memoryIndex)
           ? (mem32 ? SASigMemDiscardSharedM32 : SASigMemDiscardSharedM64)
           : (mem32 ? SASigMemDiscardM32 : SASigMemDiscardM64));
  return emitInstanceCall3(bytecodeOffset, callee, start, len, base);
}
#endif

bool FunctionCompiler::emitTableGet() {
  uint32_t tableIndex;
  MDefinition* address;
  if (!iter().readTableGet(&tableIndex, &address)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  const TableDesc& table = codeMeta().tables[tableIndex];

  if (table.elemType().tableRepr() == TableRepr::Ref) {
    MDefinition* ret = tableGetAnyRef(tableIndex, address);
    if (!ret) {
      return false;
    }
    iter().setResult(ret);
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();

  MDefinition* address32 = clampTableAddressToI32(table.addressType(), address);
  if (!address32) {
    return false;
  }

  MDefinition* tableIndexArg = constantI32(int32_t(tableIndex));
  if (!tableIndexArg) {
    return false;
  }

  // The return value here is either null, denoting an error, or a short-lived
  // pointer to a location containing a possibly-null ref.
  MDefinition* ret;
  if (!emitInstanceCall2(bytecodeOffset, SASigTableGet, address32,
                         tableIndexArg, &ret)) {
    return false;
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitTableGrow() {
  uint32_t tableIndex;
  MDefinition* initValue;
  MDefinition* delta;
  if (!iter().readTableGrow(&tableIndex, &initValue, &delta)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();
  const TableDesc& table = codeMeta().tables[tableIndex];

  MDefinition* delta32 = clampTableAddressToI32(table.addressType(), delta);
  if (!delta32) {
    return false;
  }

  MDefinition* tableIndexArg = constantI32(int32_t(tableIndex));
  if (!tableIndexArg) {
    return false;
  }

  MDefinition* ret;
  if (!emitInstanceCall3(bytecodeOffset, SASigTableGrow, initValue, delta32,
                         tableIndexArg, &ret)) {
    return false;
  }

  if (table.addressType() == AddressType::I64) {
    ret = extendI32(ret, false);
    if (!ret) {
      return false;
    }
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitTableSet() {
  uint32_t tableIndex;
  MDefinition* address;
  MDefinition* value;
  if (!iter().readTableSet(&tableIndex, &address, &value)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();

  const TableDesc& table = codeMeta().tables[tableIndex];

  if (table.elemType().tableRepr() == TableRepr::Ref) {
    return tableSetAnyRef(tableIndex, address, value, bytecodeOffset);
  }

  MDefinition* address32 = clampTableAddressToI32(table.addressType(), address);
  if (!address32) {
    return false;
  }
  MDefinition* tableIndexArg = constantI32(int32_t(tableIndex));
  if (!tableIndexArg) {
    return false;
  }

  return emitInstanceCall3(bytecodeOffset, SASigTableSet, address32, value,
                           tableIndexArg);
}

bool FunctionCompiler::emitTableSize() {
  uint32_t tableIndex;
  if (!iter().readTableSize(&tableIndex)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* length = loadTableLength(tableIndex);
  if (!length) {
    return false;
  }

  iter().setResult(length);
  return true;
}

bool FunctionCompiler::emitRefFunc() {
  uint32_t funcIndex;
  if (!iter().readRefFunc(&funcIndex)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  uint32_t bytecodeOffset = readBytecodeOffset();

  MDefinition* funcIndexArg = constantI32(int32_t(funcIndex));
  if (!funcIndexArg) {
    return false;
  }

  // The return value here is either null, denoting an error, or a short-lived
  // pointer to a location containing a possibly-null ref.
  MDefinition* ret;
  if (!emitInstanceCall1(bytecodeOffset, SASigRefFunc, funcIndexArg, &ret)) {
    return false;
  }

  iter().setResult(ret);
  return true;
}

bool FunctionCompiler::emitRefNull() {
  RefType type;
  if (!iter().readRefNull(&type)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* nullVal = constantNullRef(MaybeRefType(type));
  if (!nullVal) {
    return false;
  }
  iter().setResult(nullVal);
  return true;
}

bool FunctionCompiler::emitRefIsNull() {
  MDefinition* input;
  RefType sourceType;
  if (!iter().readRefIsNull(&input, &sourceType)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Some types are not castable (right now just continuations). The casting
  // machinery cannot handle them. So emit a direct null-check for now.
  if (!sourceType.isCastable()) {
    MDefinition* isNull = compareIsNull(input, JSOp::Eq);
    if (!isNull) {
      return false;
    }
    iter().setResult(isNull);
    return true;
  }

  // ref.is_null is implemented as a ref.test against the bottom type of the
  // input ref's hierarchy. This will codegen to a simple null comparison, but
  // allows this op to participate in other optimizations surrounding ref.test
  // and ref.cast.
  MDefinition* test = refTest(input, sourceType.bottomType());
  if (!test) {
    return false;
  }

  iter().setResult(test);
  return true;
}

//////////////////////////////////////////////////////////////////////////////
//
// Wide Arithmetic support

bool FunctionCompiler::emitI64AddSub128(bool isAdd) {
  MDefinition* xLo;
  MDefinition* xHi;
  MDefinition* yLo;
  MDefinition* yHi;
  if (!iter().readBinaryI128(&xLo, &xHi, &yLo, &yHi)) {
    return false;
  }
  if (inDeadCode()) {
    return true;
  }

  // Compute zHi:zLo = xHi:xLo +/- yHi:yLo.
  MInstruction* zHi;
  MInstruction* zLo;

#ifdef JS_64BIT
  // 64 bit implementation.  Produce inline code.
  if (isAdd) {
    zLo = MAdd::NewWasm(alloc(), xLo, yLo, MIRType::Int64);
  } else {
    zLo = MSub::NewWasm(alloc(), xLo, yLo, MIRType::Int64,
                        /*mustPreserveNaN=*/false);
  }
  if (!zLo) {
    return false;
  }
  curBlock_->add(zLo);

  zHi = MWasmAddSubI128HI64::New(alloc(), xLo, xHi, yLo, yHi, isAdd);
  if (!zHi) {
    return false;
  }
  curBlock_->add(zHi);

#else
  // 32 bit implementation.  Call a helper function.  The arguments and return
  // value are passed in Instance::baselineScratchWords_[0..7]; see
  // Instance::addSubI128 for details.
  MDefinition* storeSequence[4] = {xLo, xHi, yLo, yHi};
  for (int i = 0; i < 4; i++) {
    auto* store = MWasmStoreInstanceScratch2xI32::New(
        alloc(),
        /*byteOffset=*/i * 8, storeSequence[i], instancePointer_);
    if (!store) {
      return false;
    }
    curBlock_->add(store);
  }

  MDefinition* mIsAdd = constantI32(isAdd ? 1 : 0);
  if (!mIsAdd ||
      !emitInstanceCall1(readBytecodeOffset(), SASigAddSubI128, mIsAdd)) {
    return false;
  }

  zLo = MWasmLoadInstanceScratch2xI32::New(alloc(), /*byteOffset=*/0,
                                           instancePointer_);
  if (!zLo) {
    return false;
  }
  curBlock_->add(zLo);

  zHi = MWasmLoadInstanceScratch2xI32::New(alloc(), /*byteOffset=*/8,
                                           instancePointer_);
  if (!zHi) {
    return false;
  }
  curBlock_->add(zHi);
#endif  // JS_64BIT

  DefVector results;
  if (!results.reserve(2)) {
    return false;
  }
  results.infallibleAppend(zLo);
  results.infallibleAppend(zHi);
  iter().setResults(results.length(), results);

  return true;
}

bool FunctionCompiler::emitI64MulWide(bool isSigned) {
  MDefinition* x;
  MDefinition* y;
  if (!iter().readBinaryI64Wide(&x, &y)) {
    return false;
  }
  if (inDeadCode()) {
    return true;
  }

  // Compute zHi:zLo = x *widen y
  MInstruction* zLo;
  MInstruction* zHi;

#ifdef JS_64BIT
  // 64 bit implementation.  Produce inline code.

  // We can compute the low and high halves in either order.  However, the
  // RiscV specification advises computing the high half first, in the hope
  // that microarchitectures can merge it with the immediately following low
  // half multiply, hence avoiding the duplicate multiply.
  zHi = MWasmMulI64WideHI64::New(alloc(), x, y, isSigned);
  if (!zHi) {
    return false;
  }
  curBlock_->add(zHi);

  zLo = MMul::NewWasm(alloc(), x, y, MIRType::Int64,
                      /*mode=*/MMul::Normal, /*mustPreserveNaN=*/false);
  if (!zLo) {
    return false;
  }
  curBlock_->add(zLo);

#else
  // 32 bit implementation.  Call a helper function.  The arguments and return
  // value are passed in Instance::baselineScratchWords_[0..4]; see
  // Instance::mulI64Wide for details.
  auto* store = MWasmStoreInstanceScratch2xI32::New(alloc(),
                                                    /*byteOffset=*/0, x,
                                                    instancePointer_);
  if (!store) {
    return false;
  }
  curBlock_->add(store);

  store = MWasmStoreInstanceScratch2xI32::New(alloc(),
                                              /*byteOffset=*/8, y,
                                              instancePointer_);
  if (!store) {
    return false;
  }
  curBlock_->add(store);

  MDefinition* mIsSigned = constantI32(isSigned ? 1 : 0);
  if (!mIsSigned ||
      !emitInstanceCall1(readBytecodeOffset(), SASigMulI64Wide, mIsSigned)) {
    return false;
  }

  zLo = MWasmLoadInstanceScratch2xI32::New(alloc(), /*byteOffset=*/0,
                                           instancePointer_);
  if (!zLo) {
    return false;
  }
  curBlock_->add(zLo);

  zHi = MWasmLoadInstanceScratch2xI32::New(alloc(), /*byteOffset=*/8,
                                           instancePointer_);
  if (!zHi) {
    return false;
  }
  curBlock_->add(zHi);
#endif

  DefVector results;
  if (!results.reserve(2)) {
    return false;
  }
  results.infallibleAppend(zLo);
  results.infallibleAppend(zHi);
  iter().setResults(results.length(), results);

  return true;
}

//////////////////////////////////////////////////////////////////////////////
//
// SIMD support

#ifdef ENABLE_WASM_SIMD
bool FunctionCompiler::emitConstSimd128() {
  V128 v128;
  if (!iter().readV128Const(&v128)) {
    return false;
  }

  iter().setResult(constantV128(v128));
  return true;
}

bool FunctionCompiler::emitBinarySimd128(bool commutative, SimdOp op) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readBinary(ValType::V128, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(binarySimd128(lhs, rhs, commutative, op));
  return true;
}

bool FunctionCompiler::emitTernarySimd128(wasm::SimdOp op) {
  MDefinition* v0;
  MDefinition* v1;
  MDefinition* v2;
  if (!iter().readTernary(ValType::V128, &v0, &v1, &v2)) {
    return false;
  }

  iter().setResult(ternarySimd128(v0, v1, v2, op));
  return true;
}

bool FunctionCompiler::emitShiftSimd128(SimdOp op) {
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readVectorShift(&lhs, &rhs)) {
    return false;
  }

  iter().setResult(shiftSimd128(lhs, rhs, op));
  return true;
}

bool FunctionCompiler::emitSplatSimd128(ValType inType, SimdOp op) {
  MDefinition* src;
  if (!iter().readConversion(inType, ValType::V128, &src)) {
    return false;
  }

  iter().setResult(scalarToSimd128(src, op));
  return true;
}

bool FunctionCompiler::emitUnarySimd128(SimdOp op) {
  MDefinition* src;
  if (!iter().readUnary(ValType::V128, &src)) {
    return false;
  }

  iter().setResult(unarySimd128(src, op));
  return true;
}

bool FunctionCompiler::emitReduceSimd128(SimdOp op) {
  MDefinition* src;
  if (!iter().readConversion(ValType::V128, ValType::I32, &src)) {
    return false;
  }

  iter().setResult(reduceSimd128(src, op, ValType::I32));
  return true;
}

bool FunctionCompiler::emitExtractLaneSimd128(ValType outType,
                                              uint32_t laneLimit, SimdOp op) {
  uint32_t laneIndex;
  MDefinition* src;
  if (!iter().readExtractLane(outType, laneLimit, &laneIndex, &src)) {
    return false;
  }

  iter().setResult(reduceSimd128(src, op, outType, laneIndex));
  return true;
}

bool FunctionCompiler::emitReplaceLaneSimd128(ValType laneType,
                                              uint32_t laneLimit, SimdOp op) {
  uint32_t laneIndex;
  MDefinition* lhs;
  MDefinition* rhs;
  if (!iter().readReplaceLane(laneType, laneLimit, &laneIndex, &lhs, &rhs)) {
    return false;
  }

  iter().setResult(replaceLaneSimd128(lhs, rhs, laneIndex, op));
  return true;
}

bool FunctionCompiler::emitShuffleSimd128() {
  MDefinition* v1;
  MDefinition* v2;
  V128 control;
  if (!iter().readVectorShuffle(&v1, &v2, &control)) {
    return false;
  }

  iter().setResult(shuffleSimd128(v1, v2, control));
  return true;
}

bool FunctionCompiler::emitLoadSplatSimd128(Scalar::Type viewType,
                                            wasm::SimdOp splatOp) {
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readLoadSplat(Scalar::byteSize(viewType), &addr)) {
    return false;
  }

  auto* ins = loadSplatSimd128(viewType, addr, splatOp);
  if (!inDeadCode() && !ins) {
    return false;
  }
  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitLoadExtendSimd128(wasm::SimdOp op) {
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readLoadExtend(&addr)) {
    return false;
  }

  auto* ins = loadExtendSimd128(addr, op);
  if (!inDeadCode() && !ins) {
    return false;
  }
  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitLoadZeroSimd128(Scalar::Type viewType,
                                           size_t numBytes) {
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readLoadSplat(numBytes, &addr)) {
    return false;
  }

  auto* ins = loadZeroSimd128(viewType, numBytes, addr);
  if (!inDeadCode() && !ins) {
    return false;
  }
  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitLoadLaneSimd128(uint32_t laneSize) {
  uint32_t laneIndex;
  MDefinition* src;
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readLoadLane(laneSize, &addr, &laneIndex, &src)) {
    return false;
  }

  auto* ins = loadLaneSimd128(laneSize, addr, laneIndex, src);
  if (!inDeadCode() && !ins) {
    return false;
  }
  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitStoreLaneSimd128(uint32_t laneSize) {
  uint32_t laneIndex;
  MDefinition* src;
  LinearMemoryAddress<MDefinition*> addr;
  if (!iter().readStoreLane(laneSize, &addr, &laneIndex, &src)) {
    return false;
  }

  storeLaneSimd128(laneSize, addr, laneIndex, src);
  return true;
}

#endif  // ENABLE_WASM_SIMD

bool FunctionCompiler::emitRefAsNonNull() {
  MDefinition* ref;
  if (!iter().readRefAsNonNull(&ref)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* ins = refAsNonNull(ref);
  if (!ins) {
    return false;
  }

  iter().setResult(ins);
  return true;
}

bool FunctionCompiler::emitBrOnNull() {
  uint32_t relativeDepth;
  ResultType type;
  DefVector values;
  MDefinition* condition;

  BranchHint branchHint =
      iter().getBranchHint(funcIndex(), relativeBytecodeOffset());
  if (branchHint == BranchHint::Invalid) {
    branchHint = BranchHint::Unlikely;
  }

  if (!iter().readBrOnNull(&relativeDepth, &type, &values, &condition)) {
    return false;
  }

  return brOnNull(relativeDepth, values, type, condition, branchHint);
}

bool FunctionCompiler::emitBrOnNonNull() {
  uint32_t relativeDepth;
  ResultType type;
  DefVector values;
  MDefinition* condition;

  BranchHint branchHint =
      iter().getBranchHint(funcIndex(), relativeBytecodeOffset());
  if (branchHint == BranchHint::Invalid) {
    branchHint = BranchHint::Likely;
  }

  if (!iter().readBrOnNonNull(&relativeDepth, &type, &values, &condition)) {
    return false;
  }

  return brOnNonNull(relativeDepth, values, type, condition, branchHint);
}

// Speculatively inline a call_refs that are likely to target the expected
// function index in this module. A fallback for if the actual callee is not
// any of the speculated expected callees is always generated. This leads to a
// control flow chain that is roughly:
//
// if (ref.func $expectedFuncIndex_1) == actualCalleeFunc:
//   (call_inline $expectedFuncIndex1)
// else if (ref.func $expectedFuncIndex_2) == actualCalleeFunc:
//   (call_inline $expectedFuncIndex2)
// ...
// else:
//   (call_ref actualCalleeFunc)
//
bool FunctionCompiler::emitSpeculativeInlineCallRef(
    uint32_t bytecodeOffset, const FuncType& funcType,
    CallRefHint expectedFuncIndices, MDefinition* actualCalleeFunc,
    const DefVector& args, DefVector* results) {
  // There must be at least one speculative target.
  MOZ_ASSERT(!expectedFuncIndices.empty());

  // Perform an up front null check on the callee function reference.
  actualCalleeFunc = refAsNonNull(actualCalleeFunc);
  if (!actualCalleeFunc) {
    return false;
  }

  constexpr size_t numElseBlocks = CallRefHint::NUM_ENTRIES + 1;
  Vector<MBasicBlock*, numElseBlocks, SystemAllocPolicy> elseBlocks;
  if (!elseBlocks.reserve(numElseBlocks)) {
    return false;
  }

  for (uint32_t i = 0; i < expectedFuncIndices.length(); i++) {
    uint32_t funcIndex = expectedFuncIndices.get(i);

    // Load the cached value of `ref.func $expectedFuncIndex` for comparing
    // against `actualCalleeFunc`. This cached value may be null if the
    // `ref.func` for the expected function has not been executed in this
    // runtime session.
    //
    // This is okay because we have done a null check on the `actualCalleeFunc`
    // already and so comparing it against a null expected callee func will
    // return false and fall back to the general case. This can only happen if
    // we've deserialized a cached module in a different session, and then run
    // the code without ever acquiring a reference to the expected function. In
    // that case, the expected callee could never be the target of this
    // call_ref, so performing the fallback path is the right thing to do
    // anyways.
    MDefinition* expectedCalleeFunc = loadCachedRefFunc(funcIndex);
    if (!expectedCalleeFunc) {
      return false;
    }

    // Check if the callee funcref we have is equals to the expected callee
    // funcref we're inlining.
    MDefinition* isExpectedCallee =
        compare(actualCalleeFunc, expectedCalleeFunc, JSOp::Eq,
                MCompare::Compare_WasmAnyRef);
    if (!isExpectedCallee) {
      return false;
    }

    // Start a 'then' block, which will have the inlined code
    MBasicBlock* elseBlock;
    if (!branchAndStartThen(isExpectedCallee, &elseBlock)) {
      return false;
    }

    // Inline the expected callee as we do with direct calls
    DefVector inlineResults;
    if (!emitInlineCall(funcType, funcIndex,
                        InliningHeuristics::CallKind::CallRef, args,
                        &inlineResults)) {
      return false;
    }

    // Push the results for joining with the 'else' block
    if (!pushDefs(inlineResults)) {
      return false;
    }

    // Switch to the 'else' block which will have, either the check for the
    // next target, or the fallback `call_ref` if we're out of targets.
    if (!switchToElse(elseBlock, &elseBlock)) {
      return false;
    }

    elseBlocks.infallibleAppend(elseBlock);
  }

  // The block that performs the fallback call should be cold.
  curBlock_->setFrequency(Frequency::Unlikely);

  DefVector callResults;
  if (!callRef(funcType, actualCalleeFunc, bytecodeOffset, args,
               &callResults)) {
    return false;
  }

  // Push the results for joining with the 'then' block
  if (!pushDefs(callResults)) {
    return false;
  }

  // Join the various branches together
  for (uint32_t i = elseBlocks.length() - 1; i != 0; i--) {
    DefVector results;
    if (!joinIfElse(elseBlocks[i], &results) || !pushDefs(results)) {
      return false;
    }
  }
  return joinIfElse(elseBlocks[0], results);
}

bool FunctionCompiler::emitCallRef() {
  uint32_t bytecodeOffset = readBytecodeOffset();
  uint32_t funcTypeIndex;
  MDefinition* callee;
  DefVector args;

  if (!iter().readCallRef(&funcTypeIndex, &callee, &args)) {
    return false;
  }

  // We must unconditionally read a call_ref hint so that we stay in sync with
  // how baseline generates them.
  CallRefHint hint = readCallRefHint();

  if (inDeadCode()) {
    return true;
  }

  const FuncType& funcType = codeMeta().types->type(funcTypeIndex).funcType();

  // Ask the inlining heuristics which entries in `hint` we are allowed to
  // inline.
  CallRefHint approved =
      auditInlineableCallees(InliningHeuristics::CallKind::CallRef, hint);
  if (!approved.empty()) {
    DefVector results;
    if (!emitSpeculativeInlineCallRef(bytecodeOffset, funcType, approved,
                                      callee, args, &results)) {
      return false;
    }
    iter().setResults(results.length(), results);
    return true;
  }

  DefVector results;
  if (!callRef(funcType, callee, bytecodeOffset, args, &results)) {
    return false;
  }

  iter().setResults(results.length(), results);
  return true;
}

bool FunctionCompiler::emitStructNew() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex;
  DefVector args;
  if (!iter().readStructNew(&typeIndex, &args)) {
    return false;
  }

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  const TypeDef& typeDef = (*codeMeta().types)[typeIndex];
  const StructType& structType = typeDef.structType();
  MOZ_ASSERT(args.length() == structType.fields_.length());

  MDefinition* structObject =
      createStructObject(typeIndex, allocSiteIndex, false);
  if (!structObject) {
    return false;
  }

  // And fill in the fields.
  for (uint32_t fieldIndex = 0; fieldIndex < structType.fields_.length();
       fieldIndex++) {
    if (!mirGen().ensureBallast()) {
      return false;
    }
    if (!writeValueToStructField(bytecodeOffset, structType, fieldIndex,
                                 structObject, args[fieldIndex],
                                 WasmPreBarrierKind::None)) {
      return false;
    }
  }

  iter().setResult(structObject);
  return true;
}

bool FunctionCompiler::emitStructNewDefault() {
  uint32_t typeIndex;
  if (!iter().readStructNewDefault(&typeIndex)) {
    return false;
  }

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  MDefinition* structObject =
      createStructObject(typeIndex, allocSiteIndex, true);
  if (!structObject) {
    return false;
  }

  iter().setResult(structObject);
  return true;
}

bool FunctionCompiler::emitStructSet() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex;
  uint32_t fieldIndex;
  MDefinition* structObject;
  MDefinition* value;
  if (!iter().readStructSet(&typeIndex, &fieldIndex, &structObject, &value)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Check for null is done at writeValueToStructField.

  // And fill in the field.
  const StructType& structType = (*codeMeta().types)[typeIndex].structType();
  return writeValueToStructField(bytecodeOffset, structType, fieldIndex,
                                 structObject, value,
                                 WasmPreBarrierKind::Normal);
}

bool FunctionCompiler::emitStructGet(FieldWideningOp wideningOp) {
  uint32_t typeIndex;
  uint32_t fieldIndex;
  MDefinition* structObject;
  if (!iter().readStructGet(&typeIndex, &fieldIndex, wideningOp,
                            &structObject)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Check for null is done at readValueFromStructField.

  // And fetch the data.
  const StructType& structType = (*codeMeta().types)[typeIndex].structType();
  MDefinition* load = readValueFromStructField(structType, fieldIndex,
                                               wideningOp, structObject);
  if (!load) {
    return false;
  }

  iter().setResult(load);
  return true;
}

bool FunctionCompiler::emitArrayNew() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex;
  MDefinition* numElements;
  MDefinition* fillValue;
  if (!iter().readArrayNew(&typeIndex, &numElements, &fillValue)) {
    return false;
  }

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  // Create the array object, uninitialized.
  MDefinition* arrayObject =
      createArrayObject(typeIndex, allocSiteIndex, numElements,
                        /*zeroFields=*/false);
  if (!arrayObject) {
    return false;
  }

  const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();
  if (!fillArray(bytecodeOffset, arrayType, arrayObject, constantI32(0),
                 numElements, fillValue, WasmPreBarrierKind::None,
                 WasmPostBarrierKind::None)) {
    return false;
  }

  if (arrayType.elementType().isRefRepr()) {
    // Emit one whole-cell post barrier for the whole array, since there is just
    // one object and one value.
    if (!postBarrierWholeCell(bytecodeOffset, arrayObject, fillValue)) {
      return false;
    }
  }

  iter().setResult(arrayObject);
  return true;
}

bool FunctionCompiler::emitArrayNewDefault() {
  // This is almost identical to EmitArrayNew, except we skip the
  // initialisation loop.
  uint32_t typeIndex;
  MDefinition* numElements;
  if (!iter().readArrayNewDefault(&typeIndex, &numElements)) {
    return false;
  }

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  // Create the array object, default-initialized.
  MDefinition* arrayObject =
      createArrayObject(typeIndex, allocSiteIndex, numElements,
                        /*zeroFields=*/true);
  if (!arrayObject) {
    return false;
  }

  iter().setResult(arrayObject);
  return true;
}

bool FunctionCompiler::emitArrayNewFixed() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex, numElements;
  DefVector values;

  if (!iter().readArrayNewFixed(&typeIndex, &numElements, &values)) {
    return false;
  }
  MOZ_ASSERT(values.length() == numElements);

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  MDefinition* numElementsDef = constantI32(int32_t(numElements));
  if (!numElementsDef) {
    return false;
  }

  // Create the array object, uninitialized.
  const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();
  StorageType elemType = arrayType.elementType();
  uint32_t elemSize = elemType.size();
  MDefinition* arrayObject =
      createArrayObject(typeIndex, allocSiteIndex, numElementsDef,
                        /*zeroFields=*/false);
  if (!arrayObject) {
    return false;
  }

  // Make `base` point at the first byte of the (OOL) data area.
  MDefinition* base = getWasmArrayObjectData(arrayObject);
  if (!base) {
    return false;
  }

  // Write each element in turn.

  // How do we know that the offset expression `i * elemSize` below remains
  // within 2^31 (signed-i32) range?  In the worst case we will have 16-byte
  // values, and there can be at most MaxFunctionBytes expressions, if it were
  // theoretically possible to generate one expression per instruction byte.
  // Hence the max offset we can be expected to generate is
  // `16 * MaxFunctionBytes`.
  static_assert(16 /* sizeof v128 */ * MaxFunctionBytes <=
                MaxArrayPayloadBytes);
  MOZ_RELEASE_ASSERT(numElements <= MaxFunctionBytes);

  for (uint32_t i = 0; i < numElements; i++) {
    if (!mirGen().ensureBallast()) {
      return false;
    }
    // `i * elemSize` is made safe by the assertions above.
    if (!writeGcValueAtBasePlusOffset(
            bytecodeOffset, elemType, arrayObject, AliasSet::WasmArrayDataArea,
            values[numElements - 1 - i], base, i * elemSize, i, false,
            WasmPreBarrierKind::None, WasmPostBarrierKind::WholeCell)) {
      return false;
    }
  }

  iter().setResult(arrayObject);
  return true;
}

bool FunctionCompiler::emitArrayNewData() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex, segIndex;
  MDefinition* segByteOffset;
  MDefinition* numElements;
  if (!iter().readArrayNewData(&typeIndex, &segIndex, &segByteOffset,
                               &numElements)) {
    return false;
  }

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  MDefinition* typeIndexValue = constantI32(int32_t(typeIndex));
  if (!typeIndexValue) {
    return false;
  }

  MDefinition* allocSite = loadAllocSiteInstanceData(allocSiteIndex);
  if (!allocSite) {
    return false;
  }

  // Other values we need to pass to the instance call:
  MDefinition* segIndexM = constantI32(int32_t(segIndex));
  if (!segIndexM) {
    return false;
  }

  // Create call:
  // arrayObject = Instance::arrayNewData(segByteOffset:u32, numElements:u32,
  //                                      typeDefData:word, segIndex:u32)
  // If the requested size exceeds MaxArrayPayloadBytes, the MIR generated by
  // this call will trap.
  MDefinition* arrayObject;
  if (!emitInstanceCall5(bytecodeOffset, SASigArrayNewData, segByteOffset,
                         numElements, typeIndexValue, allocSite, segIndexM,
                         &arrayObject)) {
    return false;
  }

  iter().setResult(arrayObject);
  return true;
}

bool FunctionCompiler::emitArrayNewElem() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex, segIndex;
  MDefinition* segElemIndex;
  MDefinition* numElements;
  if (!iter().readArrayNewElem(&typeIndex, &segIndex, &segElemIndex,
                               &numElements)) {
    return false;
  }

  uint32_t allocSiteIndex = readAllocSiteIndex(typeIndex);

  if (inDeadCode()) {
    return true;
  }

  MDefinition* typeIndexValue = constantI32(int32_t(typeIndex));
  if (!typeIndexValue) {
    return false;
  }

  MDefinition* allocSite = loadAllocSiteInstanceData(allocSiteIndex);
  if (!allocSite) {
    return false;
  }

  // Other values we need to pass to the instance call:
  MDefinition* segIndexM = constantI32(int32_t(segIndex));
  if (!segIndexM) {
    return false;
  }

  // Create call:
  // arrayObject = Instance::arrayNewElem(segElemIndex:u32, numElements:u32,
  //                                      typeDefData:word, segIndex:u32)
  // If the requested size exceeds MaxArrayPayloadBytes, the MIR generated by
  // this call will trap.
  MDefinition* arrayObject;
  if (!emitInstanceCall5(bytecodeOffset, SASigArrayNewElem, segElemIndex,
                         numElements, typeIndexValue, allocSite, segIndexM,
                         &arrayObject)) {
    return false;
  }

  iter().setResult(arrayObject);
  return true;
}

bool FunctionCompiler::emitArrayInitData() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t unusedTypeIndex, segIndex;
  MDefinition* array;
  MDefinition* arrayIndex;
  MDefinition* segOffset;
  MDefinition* length;
  if (!iter().readArrayInitData(&unusedTypeIndex, &segIndex, &array,
                                &arrayIndex, &segOffset, &length)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Other values we need to pass to the instance call:
  MDefinition* segIndexM = constantI32(int32_t(segIndex));
  if (!segIndexM) {
    return false;
  }

  // Create call:
  // Instance::arrayInitData(array:word, index:u32, segByteOffset:u32,
  // numElements:u32, segIndex:u32) If the requested size exceeds
  // MaxArrayPayloadBytes, the MIR generated by this call will trap.
  return emitInstanceCall5(bytecodeOffset, SASigArrayInitData, array,
                           arrayIndex, segOffset, length, segIndexM);
}

bool FunctionCompiler::emitArrayInitElem() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex, segIndex;
  MDefinition* array;
  MDefinition* arrayIndex;
  MDefinition* segOffset;
  MDefinition* length;
  if (!iter().readArrayInitElem(&typeIndex, &segIndex, &array, &arrayIndex,
                                &segOffset, &length)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* typeIndexValue = constantI32(int32_t(typeIndex));
  if (!typeIndexValue) {
    return false;
  }

  // Other values we need to pass to the instance call:
  MDefinition* segIndexM = constantI32(int32_t(segIndex));
  if (!segIndexM) {
    return false;
  }

  // Create call:
  // Instance::arrayInitElem(array:word, index:u32, segByteOffset:u32,
  // numElements:u32, typeDefData:word, segIndex:u32) If the requested size
  // exceeds MaxArrayPayloadBytes, the MIR generated by this call will trap.
  return emitInstanceCall6(bytecodeOffset, SASigArrayInitElem, array,
                           arrayIndex, segOffset, length, typeIndexValue,
                           segIndexM);
}

bool FunctionCompiler::emitArraySet() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex;
  MDefinition* value;
  MDefinition* index;
  MDefinition* arrayObject;
  if (!iter().readArraySet(&typeIndex, &value, &index, &arrayObject)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Check for null is done at setupForArrayAccess.

  // Create the object null check and the array bounds check and get the OOL
  // data pointer.
  MDefinition* base = setupForArrayAccess(arrayObject, index);
  if (!base) {
    return false;
  }

  // And do the store.
  const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();
  StorageType elemType = arrayType.elementType();
  uint32_t elemSize = elemType.size();
  MOZ_ASSERT(elemSize >= 1 && elemSize <= 16);

  return writeGcValueAtBasePlusScaledIndex(
      bytecodeOffset, elemType, arrayObject, AliasSet::WasmArrayDataArea, value,
      base, elemSize, index, WasmPreBarrierKind::Normal,
      WasmPostBarrierKind::Edge);
}

bool FunctionCompiler::emitArrayGet(FieldWideningOp wideningOp) {
  uint32_t typeIndex;
  MDefinition* index;
  MDefinition* arrayObject;
  if (!iter().readArrayGet(&typeIndex, wideningOp, &index, &arrayObject)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Check for null is done at setupForArrayAccess.

  // Create the object null check and the array bounds check and get the data
  // pointer.
  MDefinition* base = setupForArrayAccess(arrayObject, index);
  if (!base) {
    return false;
  }

  // And do the load.
  const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();
  StorageType elemType = arrayType.elementType();

  MDefinition* load =
      readGcArrayValueAtIndex(elemType, wideningOp, arrayObject,
                              AliasSet::WasmArrayDataArea, base, index);
  if (!load) {
    return false;
  }

  iter().setResult(load);
  return true;
}

bool FunctionCompiler::emitArrayLen() {
  MDefinition* arrayObject;
  if (!iter().readArrayLen(&arrayObject)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // Check for null is done at getWasmArrayObjectNumElements.

  // Get the size value for the array
  MDefinition* numElements = getWasmArrayObjectNumElements(arrayObject);
  if (!numElements) {
    return false;
  }

  iter().setResult(numElements);
  return true;
}

bool FunctionCompiler::emitArrayCopy() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t dstArrayTypeIndex;
  uint32_t srcArrayTypeIndex;
  MDefinition* dstArrayObject;
  MDefinition* dstArrayIndex;
  MDefinition* srcArrayObject;
  MDefinition* srcArrayIndex;
  MDefinition* numElements;
  if (!iter().readArrayCopy(&dstArrayTypeIndex, &srcArrayTypeIndex,
                            &dstArrayObject, &dstArrayIndex, &srcArrayObject,
                            &srcArrayIndex, &numElements)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  const ArrayType& dstArrayType =
      codeMeta().types->type(dstArrayTypeIndex).arrayType();
  StorageType dstElemType = dstArrayType.elementType();
  int32_t elemSize = int32_t(dstElemType.size());
  bool elemsAreRefTyped = dstElemType.isRefType();

  return createArrayCopy(bytecodeOffset, dstArrayObject, dstArrayIndex,
                         srcArrayObject, srcArrayIndex, numElements, elemSize,
                         elemsAreRefTyped);
}

bool FunctionCompiler::emitArrayFill() {
  uint32_t bytecodeOffset = readBytecodeOffset();

  uint32_t typeIndex;
  MDefinition* array;
  MDefinition* index;
  MDefinition* val;
  MDefinition* numElements;
  if (!iter().readArrayFill(&typeIndex, &array, &index, &val, &numElements)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  return createArrayFill(bytecodeOffset, typeIndex, array, index, val,
                         numElements);
}

bool FunctionCompiler::emitRefI31() {
  MDefinition* input;
  if (!iter().readConversion(ValType::I32,
                             ValType(RefType::i31().asNonNullable()), &input)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* output = refI31(input);
  if (!output) {
    return false;
  }
  iter().setResult(output);
  return true;
}

bool FunctionCompiler::emitI31Get(FieldWideningOp wideningOp) {
  MOZ_ASSERT(wideningOp != FieldWideningOp::None);

  MDefinition* input;
  if (!iter().readConversion(ValType(RefType::i31()), ValType::I32, &input)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  input = refAsNonNull(input);
  if (!input) {
    return false;
  }
  MDefinition* output = i31Get(input, wideningOp);
  if (!output) {
    return false;
  }
  iter().setResult(output);
  return true;
}

bool FunctionCompiler::emitRefTest(bool nullable) {
  MDefinition* ref;
  RefType sourceType;
  RefType destType;
  if (!iter().readRefTest(nullable, &sourceType, &destType, &ref)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* success = refTest(ref, destType);
  if (!success) {
    return false;
  }

  iter().setResult(success);
  return true;
}

bool FunctionCompiler::emitRefCast(bool nullable) {
  MDefinition* ref;
  RefType sourceType;
  RefType destType;
  if (!iter().readRefCast(nullable, &sourceType, &destType, &ref)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* castedRef = refCast(ref, destType);
  if (!castedRef) {
    return false;
  }

  iter().setResult(castedRef);
  return true;
}

bool FunctionCompiler::emitBrOnCast(bool onSuccess) {
  uint32_t labelRelativeDepth;
  RefType sourceType;
  RefType destType;
  ResultType labelType;
  DefVector values;

  BranchHint branchHint =
      iter().getBranchHint(funcIndex(), relativeBytecodeOffset());
  if (branchHint == BranchHint::Invalid) {
    branchHint = onSuccess ? BranchHint::Likely : BranchHint::Unlikely;
  }

  if (!iter().readBrOnCast(onSuccess, &labelRelativeDepth, &sourceType,
                           &destType, &labelType, &values)) {
    return false;
  }

  return brOnCastCommon(onSuccess, labelRelativeDepth, sourceType, destType,
                        labelType, values, branchHint);
}

bool FunctionCompiler::emitAnyConvertExtern() {
  MDefinition* ref;
  if (!iter().readRefConversion(RefType::extern_(), RefType::any(), &ref)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* conversion = convertAnyExtern(ref, wasm::RefType::Kind::Any);
  if (!conversion) {
    return false;
  }

  iter().setResult(conversion);
  return true;
}

bool FunctionCompiler::emitExternConvertAny() {
  MDefinition* ref;
  if (!iter().readRefConversion(RefType::any(), RefType::extern_(), &ref)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* conversion = convertAnyExtern(ref, wasm::RefType::Kind::Extern);
  if (!conversion) {
    return false;
  }

  iter().setResult(conversion);
  return true;
}

bool FunctionCompiler::emitCallBuiltinModuleFunc() {
  const BuiltinModuleFunc* builtinModuleFunc;

  DefVector params;
  if (!iter().readCallBuiltinModuleFunc(&builtinModuleFunc, &params)) {
    return false;
  }

  return callBuiltinModuleFunc(*builtinModuleFunc, params);
}

#ifdef ENABLE_WASM_JSPI
bool FunctionCompiler::emitContNew() {
  uint32_t typeIndex;
  MDefinition* func;
  if (!iter().readContNew(&typeIndex, &func)) {
    return false;
  }

  const TypeDef& typeDef = codeMeta().types->type(typeIndex);
  const ContType& contType = typeDef.contType();

  // TODO: Temporary restriction that cont type cannot have params or results.
  if (!contType.funcType().args().empty() ||
      !contType.funcType().results().empty()) {
    unimplementedTrap();
    return true;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* result = nullptr;
  if (!emitInstanceCall1(readBytecodeOffset(), SASigContNew, func, &result)) {
    return false;
  }
  iter().setResult(result);

  return true;
}

bool FunctionCompiler::emitContBind() {
  uint32_t inputContTypeIndex;
  uint32_t outputContTypeIndex;
  DefVector boundArgs;
  MDefinition* cont;
  if (!iter().readContBind(&inputContTypeIndex, &outputContTypeIndex,
                           &boundArgs, &cont)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // TODO: Not yet implemented
  unimplementedTrap();
  return true;
}

bool FunctionCompiler::emitStoreSuspendParams(
    MDefinition* paramsArea, const ValTypeVector& suspendTagParams,
    const DefVector& suspendParams, MDefinition* suspendedCont) {
  // Last param is the cont, it goes first in stack results.
  size_t paramsAreaOffset = 0;
  MWasmStackResultArea::StackResult loc(paramsAreaOffset,
                                        js::jit::MIRType::WasmAnyRef);
  MWasmStoreStackResult* storeCont = MWasmStoreStackResult::New(
      alloc(), paramsArea, paramsAreaOffset, suspendedCont);
  if (!storeCont) {
    return false;
  }
  curBlock_->add(storeCont);
  paramsAreaOffset = loc.endOffset();

  // The rest are suspendTagParams, again in reverse order.
  for (uint32_t i = 0; i < suspendTagParams.length(); i++) {
    if (!mirGen().ensureBallast()) {
      return false;
    }
    size_t reverseIndex = suspendTagParams.length() - i - 1;

    ValType handlerParam = suspendTagParams[reverseIndex];
    MWasmStackResultArea::StackResult loc(paramsAreaOffset,
                                          handlerParam.toMIRType());

    MDefinition* param = suspendParams[reverseIndex];
    MWasmStoreStackResult* store = MWasmStoreStackResult::New(
        alloc(), paramsArea, paramsAreaOffset, param);
    if (!store) {
      return false;
    }
    curBlock_->add(store);

    paramsAreaOffset = loc.endOffset();
  }
  return true;
}

bool FunctionCompiler::emitSuspend() {
  uint32_t tagIndex;
  DefVector suspendParams;
  if (!iter().readSuspend(&tagIndex, &suspendParams)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  const TagDesc& tagDesc = codeMeta().tags[tagIndex];
  const TagType& tagType = *tagDesc.type;

  // TODO: Temporary restriction that suspend tags cannot have results.
  if (!tagType.resultTypes().empty()) {
    unimplementedTrap();
    return true;
  }

  // TODO: Temporary restriction that we can't be in a try block yet. A
  // resume_throw will be able to trigger an exception that we need to handle.
  if (inTryCode()) {
    unimplementedTrap();
    return true;
  }

  // Load the tag we're going to search for.
  MDefinition* tag = loadTag(tagIndex);
  if (!tag) {
    return false;
  }

  // Search for the handler for this tag. When this suspend is used for JS-PI
  // this find handler will be infallible because we emit a different find
  // handler in emitGuardSuspending (which throws an exception instead of a
  // trap).
  MWasmFindHandler* handler =
      MWasmFindHandler::New(alloc(), instancePointer_, tag,
                            Trap::NullPointerDereference, trapSiteDesc());
  if (!handler) {
    return false;
  }
  curBlock_->add(handler);

  // Load the paramsArea from the handler.
  MWasmLoadInstance* paramsArea =
      MWasmLoadInstance::New(alloc(), handler,
                             offsetof(wasm::Handler, target) +
                                 offsetof(wasm::SwitchTarget, paramsArea),
                             MIRType::Pointer, AliasSet::None());
  if (!paramsArea) {
    return false;
  }
  curBlock_->add(paramsArea);

  // Allocate a new continuation that will hold our stack when we suspend.
  MDefinition* suspendedCont = nullptr;
  if (!emitInstanceCall0(readBytecodeOffset(), SASigContNewEmpty,
                         &suspendedCont)) {
    return false;
  }

  // Store all the params into the handler params area.
  if (!emitStoreSuspendParams(paramsArea, tagType.argTypes(), suspendParams,
                              suspendedCont)) {
    return false;
  }

  // Emit the suspend instruction.
  MWasmSuspend* suspend =
      MWasmSuspend::New(alloc(), instancePointer_, suspendedCont, handler,
                        callSiteDesc(CallSiteKind::StackSwitch));
  if (!suspend) {
    return false;
  }
  curBlock_->add(suspend);

  return true;
}

bool FunctionCompiler::emitResume() {
  uint32_t typeIndex;
  MDefinition* cont;
  DefVector args;
  HandlerExprVector handlers;
  if (!iter().readResume(&typeIndex, &handlers, &args, &cont)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // TODO: Temporary restriction that cont type cannot have params or results.
  const TypeDef& typeDef = codeMeta().types->type(typeIndex);
  const ContType& contType = typeDef.contType();
  if (!contType.funcType().args().empty() ||
      !contType.funcType().results().empty()) {
    unimplementedTrap();
    return true;
  }

  // TODO: Temporary restriction that suspend tags cannot have params or
  // results.
  for (const HandlerExpr& handler : handlers) {
    const TagDesc& tagDesc = codeMeta().tags[handler.tagIndex()];
    const TagType& tagType = *tagDesc.type;

    if (handler.isSwitch() || !tagType.resultTypes().empty()) {
      unimplementedTrap();
      return true;
    }
  }

  // Start with a resume barrier which will mark the stack if it hasn't yet and
  // we're in an incremental GC.
  MWasmResumeBarrier* barrier =
      MWasmResumeBarrier::New(alloc(), instancePointer_, cont);
  if (!barrier) {
    return false;
  }
  curBlock_->add(barrier);

  MBasicBlock* fallthroughBlock = nullptr;
  MBasicBlock* prePadBlock = nullptr;

  ControlInstructionVector* tryLandingPadPatches = nullptr;
  mozilla::Maybe<uint32_t> tryNote;
  if (inTryBlock(&tryLandingPadPatches)) {
    tryNote.emplace(0);
    if (!rootCompiler_.addTryNote(tryNote.ptr())) {
      return false;
    }
    if (!newBlock(curBlock_, &prePadBlock)) {
      return false;
    }
  }

  if (!newBlock(curBlock_, &fallthroughBlock)) {
    return false;
  }

  size_t numResultsAreaItems = 0;
  for (size_t i = 0; i < handlers.length(); i++) {
    const HandlerExpr& handler = handlers[i];
    MOZ_ASSERT(!handler.isSwitch());
    numResultsAreaItems +=
        1 + codeMeta().getTagType(handler.tagIndex()).argTypes().length();
  }

  MWasmStackResultArea* handlersResultArea = nullptr;
  if (numResultsAreaItems) {
    handlersResultArea = MWasmStackResultArea::New(alloc());
    if (!handlersResultArea ||
        !handlersResultArea->init(alloc(), numResultsAreaItems)) {
      return false;
    }
    curBlock_->add(handlersResultArea);
  }

  MWasmResume* resume =
      MWasmResume::New(alloc(), callSiteDesc(CallSiteKind::StackSwitch),
                       tryNote, instancePointer_, cont, handlersResultArea);
  if (!resume ||
      !resume->init(fallthroughBlock, prePadBlock, handlers.length())) {
    return false;
  }
  curBlock_->end(resume);
  MBasicBlock* resumeBlock = curBlock_;
  curBlock_ = nullptr;

  // emitHandlerLandingPads
  size_t currentResultsAreaIndex = 0;
  size_t currentResultsAreaOffset = 0;
  for (size_t i = 0; i < handlers.length(); i++) {
    MOZ_ASSERT(!curBlock_);

    const HandlerExpr& handler = handlers[i];
    MOZ_ASSERT(!handler.isSwitch());

    uint32_t baseResultsAreaByteOffset = currentResultsAreaOffset;
    const TagDesc& tagDesc = codeMeta().tags[handler.tagIndex()];
    const TagType& tagType = *tagDesc.type;
    ResultType suspendTagParams = tagType.argResultType();

    // Last param is the cont, it goes first in stack results.
    MWasmStackResultArea::StackResult loc(currentResultsAreaOffset,
                                          js::jit::MIRType::WasmAnyRef);
    handlersResultArea->initResult(currentResultsAreaIndex, loc);
    currentResultsAreaIndex++;
    currentResultsAreaOffset = loc.endOffset();

    // The rest are suspendTagParams, again in reverse order.
    for (uint32_t i = 0; i < suspendTagParams.length(); i++) {
      size_t reverseIndex = suspendTagParams.length() - i - 1;
      ValType handlerParam = suspendTagParams[reverseIndex];

      MWasmStackResultArea::StackResult loc(currentResultsAreaOffset,
                                            handlerParam.toMIRType());
      handlersResultArea->initResult(currentResultsAreaIndex, loc);
      currentResultsAreaIndex++;
      currentResultsAreaOffset = loc.endOffset();
    }

    MBasicBlock* target;
    if (!newBlock(resumeBlock, &target)) {
      return false;
    }
    curBlock_ = target;

    if (!resume->initHandler(
            i, codeMeta().offsetOfTagInstanceData(handler.tagIndex()),
            baseResultsAreaByteOffset, target)) {
      return false;
    }

    DefVector resultDefs;
    size_t suspendLabelParams = suspendTagParams.length() + 1;
    for (uint32_t i = 0; i < suspendLabelParams; i++) {
      if (!mirGen().ensureBallast()) {
        return false;
      }
      size_t stackResultIndex = currentResultsAreaIndex - i - 1;

      MWasmStackResult* stackResult =
          MWasmStackResult::New(alloc(), handlersResultArea, stackResultIndex);
      if (!stackResult || !resultDefs.append(stackResult)) {
        return false;
      }
      curBlock_->add(stackResult);
    }

    if (!br(handler.labelDepth(), resultDefs)) {
      return false;
    }

    curBlock_ = nullptr;
  }

  MOZ_ASSERT(!curBlock_);
  if (tryNote.isSome()) {
    // Switch to the prePadBlock
    curBlock_ = prePadBlock;

    // Mark this as the landing pad for the call
    curBlock_->add(MWasmCallLandingPrePad::New(alloc(), resumeBlock, *tryNote));

    // End with a pending jump to the landing pad
    if (!endWithPadPatch(tryLandingPadPatches)) {
      return false;
    }
  }

  // Compilation continues in the fallthroughBlock.
  curBlock_ = fallthroughBlock;
  return true;
}

bool FunctionCompiler::emitResumeThrow() {
  uint32_t typeIndex;
  uint32_t tagIndex;
  MDefinition* cont;
  DefVector args;
  HandlerExprVector handlers;
  if (!iter().readResumeThrow(&typeIndex, &tagIndex, &handlers, &args, &cont)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // TODO: Not yet implemented.
  unimplementedTrap();
  return true;
}

bool FunctionCompiler::emitResumeThrowRef() {
  uint32_t contTypeIndex;
  MDefinition* cont;
  MDefinition* exception;
  HandlerExprVector handlers;
  if (!iter().readResumeThrowRef(&contTypeIndex, &handlers, &exception,
                                 &cont)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // TODO: Not yet implemented.
  unimplementedTrap();
  return true;
}

bool FunctionCompiler::emitSwitch() {
  uint32_t contTypeIndex;
  uint32_t tagIndex;
  MDefinition* cont;
  DefVector args;
  HandlerExprVector handlers;
  if (!iter().readSwitch(&contTypeIndex, &tagIndex, &args, &cont)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  // TODO: Not yet implemented.
  unimplementedTrap();
  return true;
}

bool FunctionCompiler::emitGuardSuspending() {
  uint32_t tagIndex;
  if (!iter().readGuardSuspending(&tagIndex)) {
    return false;
  }

  if (inDeadCode()) {
    return true;
  }

  MDefinition* tag = loadTag(tagIndex);
  if (!tag) {
    return false;
  }

  // This will throw an exception (not a trap) which would require us to add
  // branches to catch blocks. However this is only used in self-hosted wasm
  // code for JS-PI where we ensure there is no try blocks.
  MOZ_ASSERT(!inTryCode());

  MWasmFindHandler* handler = MWasmFindHandler::New(
      alloc(), instancePointer_, tag, Trap::ThrowSuspendError, trapSiteDesc());
  if (!handler) {
    return false;
  }
  curBlock_->add(handler);

  return true;
}

#endif  // ENABLE_WASM_JSPI

bool FunctionCompiler::emitBodyExprs() {
  if (!iter().startFunction(funcIndex())) {
    return false;
  }

#define CHECK(c)          \
  if (!(c)) return false; \
  break

  while (true) {
    if (!mirGen().ensureBallast()) {
      return false;
    }

    OpBytes op;
    if (!iter().readOp(&op)) {
      return false;
    }

    switch (op.b0) {
      case uint16_t(Op::End):
        if (!emitEnd()) {
          return false;
        }
        if (iter().controlStackEmpty()) {
          return true;
        }
        break;

      // Control opcodes
      case uint16_t(Op::Unreachable):
        CHECK(emitUnreachable());
      case uint16_t(Op::Nop):
        CHECK(iter().readNop());
      case uint16_t(Op::Block):
        CHECK(emitBlock());
      case uint16_t(Op::Loop):
        CHECK(emitLoop());
      case uint16_t(Op::If):
        CHECK(emitIf());
      case uint16_t(Op::Else):
        CHECK(emitElse());
      case uint16_t(Op::Try):
        CHECK(emitTry());
      case uint16_t(Op::Catch):
        CHECK(emitCatch());
      case uint16_t(Op::CatchAll):
        CHECK(emitCatchAll());
      case uint16_t(Op::Delegate):
        CHECK(emitDelegate());
      case uint16_t(Op::Throw):
        CHECK(emitThrow());
      case uint16_t(Op::Rethrow):
        CHECK(emitRethrow());
      case uint16_t(Op::ThrowRef):
        CHECK(emitThrowRef());
      case uint16_t(Op::TryTable):
        CHECK(emitTryTable());
      case uint16_t(Op::Br):
        CHECK(emitBr());
      case uint16_t(Op::BrIf):
        CHECK(emitBrIf());
      case uint16_t(Op::BrTable):
        CHECK(emitBrTable());
      case uint16_t(Op::Return):
        CHECK(emitReturn());

      // Calls
      case uint16_t(Op::Call):
        CHECK(emitCall());
      case uint16_t(Op::CallIndirect):
        CHECK(emitCallIndirect());

      // Parametric operators
      case uint16_t(Op::Drop):
        CHECK(iter().readDrop());
      case uint16_t(Op::SelectNumeric):
        CHECK(emitSelect(/*typed*/ false));
      case uint16_t(Op::SelectTyped):
        CHECK(emitSelect(/*typed*/ true));

      // Locals and globals
      case uint16_t(Op::LocalGet):
        CHECK(emitGetLocal());
      case uint16_t(Op::LocalSet):
        CHECK(emitSetLocal());
      case uint16_t(Op::LocalTee):
        CHECK(emitTeeLocal());
      case uint16_t(Op::GlobalGet):
        CHECK(emitGetGlobal());
      case uint16_t(Op::GlobalSet):
        CHECK(emitSetGlobal());
      case uint16_t(Op::TableGet):
        CHECK(emitTableGet());
      case uint16_t(Op::TableSet):
        CHECK(emitTableSet());

      // Memory-related operators
      case uint16_t(Op::I32Load):
        CHECK(emitLoad(ValType::I32, Scalar::Int32));
      case uint16_t(Op::I64Load):
        CHECK(emitLoad(ValType::I64, Scalar::Int64));
      case uint16_t(Op::F32Load):
        CHECK(emitLoad(ValType::F32, Scalar::Float32));
      case uint16_t(Op::F64Load):
        CHECK(emitLoad(ValType::F64, Scalar::Float64));
      case uint16_t(Op::I32Load8S):
        CHECK(emitLoad(ValType::I32, Scalar::Int8));
      case uint16_t(Op::I32Load8U):
        CHECK(emitLoad(ValType::I32, Scalar::Uint8));
      case uint16_t(Op::I32Load16S):
        CHECK(emitLoad(ValType::I32, Scalar::Int16));
      case uint16_t(Op::I32Load16U):
        CHECK(emitLoad(ValType::I32, Scalar::Uint16));
      case uint16_t(Op::I64Load8S):
        CHECK(emitLoad(ValType::I64, Scalar::Int8));
      case uint16_t(Op::I64Load8U):
        CHECK(emitLoad(ValType::I64, Scalar::Uint8));
      case uint16_t(Op::I64Load16S):
        CHECK(emitLoad(ValType::I64, Scalar::Int16));
      case uint16_t(Op::I64Load16U):
        CHECK(emitLoad(ValType::I64, Scalar::Uint16));
      case uint16_t(Op::I64Load32S):
        CHECK(emitLoad(ValType::I64, Scalar::Int32));
      case uint16_t(Op::I64Load32U):
        CHECK(emitLoad(ValType::I64, Scalar::Uint32));
      case uint16_t(Op::I32Store):
        CHECK(emitStore(ValType::I32, Scalar::Int32));
      case uint16_t(Op::I64Store):
        CHECK(emitStore(ValType::I64, Scalar::Int64));
      case uint16_t(Op::F32Store):
        CHECK(emitStore(ValType::F32, Scalar::Float32));
      case uint16_t(Op::F64Store):
        CHECK(emitStore(ValType::F64, Scalar::Float64));
      case uint16_t(Op::I32Store8):
        CHECK(emitStore(ValType::I32, Scalar::Int8));
      case uint16_t(Op::I32Store16):
        CHECK(emitStore(ValType::I32, Scalar::Int16));
      case uint16_t(Op::I64Store8):
        CHECK(emitStore(ValType::I64, Scalar::Int8));
      case uint16_t(Op::I64Store16):
        CHECK(emitStore(ValType::I64, Scalar::Int16));
      case uint16_t(Op::I64Store32):
        CHECK(emitStore(ValType::I64, Scalar::Int32));
      case uint16_t(Op::MemorySize):
        CHECK(emitMemorySize());
      case uint16_t(Op::MemoryGrow):
        CHECK(emitMemoryGrow());

      // Constants
      case uint16_t(Op::I32Const):
        CHECK(emitI32Const());
      case uint16_t(Op::I64Const):
        CHECK(emitI64Const());
      case uint16_t(Op::F32Const):
        CHECK(emitF32Const());
      case uint16_t(Op::F64Const):
        CHECK(emitF64Const());

      // Comparison operators
      case uint16_t(Op::I32Eqz):
        CHECK(emitConversion<MNot>(ValType::I32, ValType::I32));
      case uint16_t(Op::I32Eq):
        CHECK(emitComparison(ValType::I32, JSOp::Eq, MCompare::Compare_Int32));
      case uint16_t(Op::I32Ne):
        CHECK(emitComparison(ValType::I32, JSOp::Ne, MCompare::Compare_Int32));
      case uint16_t(Op::I32LtS):
        CHECK(emitComparison(ValType::I32, JSOp::Lt, MCompare::Compare_Int32));
      case uint16_t(Op::I32LtU):
        CHECK(emitComparison(ValType::I32, JSOp::Lt, MCompare::Compare_UInt32));
      case uint16_t(Op::I32GtS):
        CHECK(emitComparison(ValType::I32, JSOp::Gt, MCompare::Compare_Int32));
      case uint16_t(Op::I32GtU):
        CHECK(emitComparison(ValType::I32, JSOp::Gt, MCompare::Compare_UInt32));
      case uint16_t(Op::I32LeS):
        CHECK(emitComparison(ValType::I32, JSOp::Le, MCompare::Compare_Int32));
      case uint16_t(Op::I32LeU):
        CHECK(emitComparison(ValType::I32, JSOp::Le, MCompare::Compare_UInt32));
      case uint16_t(Op::I32GeS):
        CHECK(emitComparison(ValType::I32, JSOp::Ge, MCompare::Compare_Int32));
      case uint16_t(Op::I32GeU):
        CHECK(emitComparison(ValType::I32, JSOp::Ge, MCompare::Compare_UInt32));
      case uint16_t(Op::I64Eqz):
        CHECK(emitConversion<MNot>(ValType::I64, ValType::I32));
      case uint16_t(Op::I64Eq):
        CHECK(emitComparison(ValType::I64, JSOp::Eq, MCompare::Compare_Int64));
      case uint16_t(Op::I64Ne):
        CHECK(emitComparison(ValType::I64, JSOp::Ne, MCompare::Compare_Int64));
      case uint16_t(Op::I64LtS):
        CHECK(emitComparison(ValType::I64, JSOp::Lt, MCompare::Compare_Int64));
      case uint16_t(Op::I64LtU):
        CHECK(emitComparison(ValType::I64, JSOp::Lt, MCompare::Compare_UInt64));
      case uint16_t(Op::I64GtS):
        CHECK(emitComparison(ValType::I64, JSOp::Gt, MCompare::Compare_Int64));
      case uint16_t(Op::I64GtU):
        CHECK(emitComparison(ValType::I64, JSOp::Gt, MCompare::Compare_UInt64));
      case uint16_t(Op::I64LeS):
        CHECK(emitComparison(ValType::I64, JSOp::Le, MCompare::Compare_Int64));
      case uint16_t(Op::I64LeU):
        CHECK(emitComparison(ValType::I64, JSOp::Le, MCompare::Compare_UInt64));
      case uint16_t(Op::I64GeS):
        CHECK(emitComparison(ValType::I64, JSOp::Ge, MCompare::Compare_Int64));
      case uint16_t(Op::I64GeU):
        CHECK(emitComparison(ValType::I64, JSOp::Ge, MCompare::Compare_UInt64));
      case uint16_t(Op::F32Eq):
        CHECK(
            emitComparison(ValType::F32, JSOp::Eq, MCompare::Compare_Float32));
      case uint16_t(Op::F32Ne):
        CHECK(
            emitComparison(ValType::F32, JSOp::Ne, MCompare::Compare_Float32));
      case uint16_t(Op::F32Lt):
        CHECK(
            emitComparison(ValType::F32, JSOp::Lt, MCompare::Compare_Float32));
      case uint16_t(Op::F32Gt):
        CHECK(
            emitComparison(ValType::F32, JSOp::Gt, MCompare::Compare_Float32));
      case uint16_t(Op::F32Le):
        CHECK(
            emitComparison(ValType::F32, JSOp::Le, MCompare::Compare_Float32));
      case uint16_t(Op::F32Ge):
        CHECK(
            emitComparison(ValType::F32, JSOp::Ge, MCompare::Compare_Float32));
      case uint16_t(Op::F64Eq):
        CHECK(emitComparison(ValType::F64, JSOp::Eq, MCompare::Compare_Double));
      case uint16_t(Op::F64Ne):
        CHECK(emitComparison(ValType::F64, JSOp::Ne, MCompare::Compare_Double));
      case uint16_t(Op::F64Lt):
        CHECK(emitComparison(ValType::F64, JSOp::Lt, MCompare::Compare_Double));
      case uint16_t(Op::F64Gt):
        CHECK(emitComparison(ValType::F64, JSOp::Gt, MCompare::Compare_Double));
      case uint16_t(Op::F64Le):
        CHECK(emitComparison(ValType::F64, JSOp::Le, MCompare::Compare_Double));
      case uint16_t(Op::F64Ge):
        CHECK(emitComparison(ValType::F64, JSOp::Ge, MCompare::Compare_Double));

      // Numeric operators
      case uint16_t(Op::I32Clz):
        CHECK(emitUnaryWithType<MClz>(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32Ctz):
        CHECK(emitUnaryWithType<MCtz>(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32Popcnt):
        CHECK(emitUnaryWithType<MPopcnt>(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32Add):
        CHECK(emitAdd(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32Sub):
        CHECK(emitSub(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32Mul):
        CHECK(emitMul(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32DivS):
      case uint16_t(Op::I32DivU):
        CHECK(emitDiv(ValType::I32, MIRType::Int32, Op(op.b0) == Op::I32DivU));
      case uint16_t(Op::I32RemS):
      case uint16_t(Op::I32RemU):
        CHECK(emitRem(ValType::I32, MIRType::Int32, Op(op.b0) == Op::I32RemU));
      case uint16_t(Op::I32And):
        CHECK(emitBitwiseAndOrXor(ValType::I32, MIRType::Int32,
                                  MWasmBinaryBitwise::SubOpcode::And));
      case uint16_t(Op::I32Or):
        CHECK(emitBitwiseAndOrXor(ValType::I32, MIRType::Int32,
                                  MWasmBinaryBitwise::SubOpcode::Or));
      case uint16_t(Op::I32Xor):
        CHECK(emitBitwiseAndOrXor(ValType::I32, MIRType::Int32,
                                  MWasmBinaryBitwise::SubOpcode::Xor));
      case uint16_t(Op::I32Shl):
        CHECK(emitShift<MLsh>(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32ShrS):
        CHECK(emitShift<MRsh>(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32ShrU):
        CHECK(emitUrsh(ValType::I32, MIRType::Int32));
      case uint16_t(Op::I32Rotl):
      case uint16_t(Op::I32Rotr):
        CHECK(emitRotate(ValType::I32, Op(op.b0) == Op::I32Rotl));
      case uint16_t(Op::I64Clz):
        CHECK(emitUnaryWithType<MClz>(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64Ctz):
        CHECK(emitUnaryWithType<MCtz>(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64Popcnt):
        CHECK(emitUnaryWithType<MPopcnt>(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64Add):
        CHECK(emitAdd(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64Sub):
        CHECK(emitSub(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64Mul):
        CHECK(emitMul(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64DivS):
      case uint16_t(Op::I64DivU):
        CHECK(emitDiv(ValType::I64, MIRType::Int64, Op(op.b0) == Op::I64DivU));
      case uint16_t(Op::I64RemS):
      case uint16_t(Op::I64RemU):
        CHECK(emitRem(ValType::I64, MIRType::Int64, Op(op.b0) == Op::I64RemU));
      case uint16_t(Op::I64And):
        CHECK(emitBitwiseAndOrXor(ValType::I64, MIRType::Int64,
                                  MWasmBinaryBitwise::SubOpcode::And));
      case uint16_t(Op::I64Or):
        CHECK(emitBitwiseAndOrXor(ValType::I64, MIRType::Int64,
                                  MWasmBinaryBitwise::SubOpcode::Or));
      case uint16_t(Op::I64Xor):
        CHECK(emitBitwiseAndOrXor(ValType::I64, MIRType::Int64,
                                  MWasmBinaryBitwise::SubOpcode::Xor));
      case uint16_t(Op::I64Shl):
        CHECK(emitShift<MLsh>(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64ShrS):
        CHECK(emitShift<MRsh>(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64ShrU):
        CHECK(emitUrsh(ValType::I64, MIRType::Int64));
      case uint16_t(Op::I64Rotl):
      case uint16_t(Op::I64Rotr):
        CHECK(emitRotate(ValType::I64, Op(op.b0) == Op::I64Rotl));
      case uint16_t(Op::F32Abs):
        CHECK(emitUnaryWithType<MAbs>(ValType::F32, MIRType::Float32));
      case uint16_t(Op::F32Neg):
        CHECK(emitUnaryWithType<MWasmNeg>(ValType::F32, MIRType::Float32));
      case uint16_t(Op::F32Ceil):
        CHECK(emitUnaryMathBuiltinCall(SASigCeilF));
      case uint16_t(Op::F32Floor):
        CHECK(emitUnaryMathBuiltinCall(SASigFloorF));
      case uint16_t(Op::F32Trunc):
        CHECK(emitUnaryMathBuiltinCall(SASigTruncF));
      case uint16_t(Op::F32Nearest):
        CHECK(emitUnaryMathBuiltinCall(SASigNearbyIntF));
      case uint16_t(Op::F32Sqrt):
        CHECK(emitUnaryWithType<MSqrt>(ValType::F32, MIRType::Float32));
      case uint16_t(Op::F32Add):
        CHECK(emitAdd(ValType::F32, MIRType::Float32));
      case uint16_t(Op::F32Sub):
        CHECK(emitSub(ValType::F32, MIRType::Float32));
      case uint16_t(Op::F32Mul):
        CHECK(emitMul(ValType::F32, MIRType::Float32));
      case uint16_t(Op::F32Div):
        CHECK(emitDiv(ValType::F32, MIRType::Float32,
                      /* isUnsigned = */ false));
      case uint16_t(Op::F32Min):
      case uint16_t(Op::F32Max):
        CHECK(emitMinMax(ValType::F32, MIRType::Float32,
                         Op(op.b0) == Op::F32Max));
      case uint16_t(Op::F32CopySign):
        CHECK(emitCopySign(ValType::F32));
      case uint16_t(Op::F64Abs):
        CHECK(emitUnaryWithType<MAbs>(ValType::F64, MIRType::Double));
      case uint16_t(Op::F64Neg):
        CHECK(emitUnaryWithType<MWasmNeg>(ValType::F64, MIRType::Double));
      case uint16_t(Op::F64Ceil):
        CHECK(emitUnaryMathBuiltinCall(SASigCeilD));
      case uint16_t(Op::F64Floor):
        CHECK(emitUnaryMathBuiltinCall(SASigFloorD));
      case uint16_t(Op::F64Trunc):
        CHECK(emitUnaryMathBuiltinCall(SASigTruncD));
      case uint16_t(Op::F64Nearest):
        CHECK(emitUnaryMathBuiltinCall(SASigNearbyIntD));
      case uint16_t(Op::F64Sqrt):
        CHECK(emitUnaryWithType<MSqrt>(ValType::F64, MIRType::Double));
      case uint16_t(Op::F64Add):
        CHECK(emitAdd(ValType::F64, MIRType::Double));
      case uint16_t(Op::F64Sub):
        CHECK(emitSub(ValType::F64, MIRType::Double));
      case uint16_t(Op::F64Mul):
        CHECK(emitMul(ValType::F64, MIRType::Double));
      case uint16_t(Op::F64Div):
        CHECK(emitDiv(ValType::F64, MIRType::Double,
                      /* isUnsigned = */ false));
      case uint16_t(Op::F64Min):
      case uint16_t(Op::F64Max):
        CHECK(
            emitMinMax(ValType::F64, MIRType::Double, Op(op.b0) == Op::F64Max));
      case uint16_t(Op::F64CopySign):
        CHECK(emitCopySign(ValType::F64));

      // Conversions
      case uint16_t(Op::I32WrapI64):
        CHECK(emitWrapI32());
      case uint16_t(Op::I32TruncF32S):
      case uint16_t(Op::I32TruncF32U):
        CHECK(emitTruncate(ValType::F32, ValType::I32,
                           Op(op.b0) == Op::I32TruncF32U, false));
      case uint16_t(Op::I32TruncF64S):
      case uint16_t(Op::I32TruncF64U):
        CHECK(emitTruncate(ValType::F64, ValType::I32,
                           Op(op.b0) == Op::I32TruncF64U, false));
      case uint16_t(Op::I64ExtendI32S):
      case uint16_t(Op::I64ExtendI32U):
        CHECK(emitExtendI32(Op(op.b0) == Op::I64ExtendI32U));
      case uint16_t(Op::I64TruncF32S):
      case uint16_t(Op::I64TruncF32U):
        CHECK(emitTruncate(ValType::F32, ValType::I64,
                           Op(op.b0) == Op::I64TruncF32U, false));
      case uint16_t(Op::I64TruncF64S):
      case uint16_t(Op::I64TruncF64U):
        CHECK(emitTruncate(ValType::F64, ValType::I64,
                           Op(op.b0) == Op::I64TruncF64U, false));
      case uint16_t(Op::F32ConvertI32S):
        CHECK(emitConversion<MToFloat32>(ValType::I32, ValType::F32));
      case uint16_t(Op::F32ConvertI32U):
        CHECK(
            emitConversion<MWasmUnsignedToFloat32>(ValType::I32, ValType::F32));
      case uint16_t(Op::F32ConvertI64S):
      case uint16_t(Op::F32ConvertI64U):
        CHECK(emitConvertI64ToFloatingPoint(ValType::F32, MIRType::Float32,
                                            Op(op.b0) == Op::F32ConvertI64U));
      case uint16_t(Op::F32DemoteF64):
        CHECK(emitConversion<MToFloat32>(ValType::F64, ValType::F32));
      case uint16_t(Op::F64ConvertI32S):
        CHECK(emitConversion<MToDouble>(ValType::I32, ValType::F64));
      case uint16_t(Op::F64ConvertI32U):
        CHECK(
            emitConversion<MWasmUnsignedToDouble>(ValType::I32, ValType::F64));
      case uint16_t(Op::F64ConvertI64S):
      case uint16_t(Op::F64ConvertI64U):
        CHECK(emitConvertI64ToFloatingPoint(ValType::F64, MIRType::Double,
                                            Op(op.b0) == Op::F64ConvertI64U));
      case uint16_t(Op::F64PromoteF32):
        CHECK(emitConversion<MToDouble>(ValType::F32, ValType::F64));

      // Reinterpretations
      case uint16_t(Op::I32ReinterpretF32):
        CHECK(emitReinterpret(ValType::I32, ValType::F32, MIRType::Int32));
      case uint16_t(Op::I64ReinterpretF64):
        CHECK(emitReinterpret(ValType::I64, ValType::F64, MIRType::Int64));
      case uint16_t(Op::F32ReinterpretI32):
        CHECK(emitReinterpret(ValType::F32, ValType::I32, MIRType::Float32));
      case uint16_t(Op::F64ReinterpretI64):
        CHECK(emitReinterpret(ValType::F64, ValType::I64, MIRType::Double));

      case uint16_t(Op::RefEq):
        CHECK(emitComparison(RefType::eq(), JSOp::Eq,
                             MCompare::Compare_WasmAnyRef));
      case uint16_t(Op::RefFunc):
        CHECK(emitRefFunc());
      case uint16_t(Op::RefNull):
        CHECK(emitRefNull());
      case uint16_t(Op::RefIsNull):
        CHECK(emitRefIsNull());

      // Sign extensions
      case uint16_t(Op::I32Extend8S):
        CHECK(emitSignExtend(1, 4));
      case uint16_t(Op::I32Extend16S):
        CHECK(emitSignExtend(2, 4));
      case uint16_t(Op::I64Extend8S):
        CHECK(emitSignExtend(1, 8));
      case uint16_t(Op::I64Extend16S):
        CHECK(emitSignExtend(2, 8));
      case uint16_t(Op::I64Extend32S):
        CHECK(emitSignExtend(4, 8));

      case uint16_t(Op::ReturnCall): {
        CHECK(emitReturnCall());
      }
      case uint16_t(Op::ReturnCallIndirect): {
        CHECK(emitReturnCallIndirect());
      }

      case uint16_t(Op::RefAsNonNull):
        CHECK(emitRefAsNonNull());
      case uint16_t(Op::BrOnNull): {
        CHECK(emitBrOnNull());
      }
      case uint16_t(Op::BrOnNonNull): {
        CHECK(emitBrOnNonNull());
      }
      case uint16_t(Op::CallRef): {
        CHECK(emitCallRef());
      }

      case uint16_t(Op::ReturnCallRef): {
        CHECK(emitReturnCallRef());
      }

#ifdef ENABLE_WASM_JSPI
      case uint16_t(Op::ContNew): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitContNew());
      }
      case uint16_t(Op::ContBind): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitContBind());
      }
      case uint16_t(Op::Suspend): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitSuspend());
      }
      case uint16_t(Op::Resume): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitResume());
      }
      case uint16_t(Op::ResumeThrow): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitResumeThrow());
      }
      case uint16_t(Op::ResumeThrowRef): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitResumeThrowRef());
      }
      case uint16_t(Op::Switch): {
        if (!codeMeta().stackSwitchingEnabled()) {
          return iter().unrecognizedOpcode(&op);
        }
        CHECK(emitSwitch());
      }
#endif  // ENABLE_WASM_JSPI

      // Gc operations
      case uint16_t(Op::GcPrefix): {
        switch (op.b1) {
          case uint32_t(GcOp::StructNew):
            CHECK(emitStructNew());
          case uint32_t(GcOp::StructNewDefault):
            CHECK(emitStructNewDefault());
          case uint32_t(GcOp::StructSet):
            CHECK(emitStructSet());
          case uint32_t(GcOp::StructGet):
            CHECK(emitStructGet(FieldWideningOp::None));
          case uint32_t(GcOp::StructGetS):
            CHECK(emitStructGet(FieldWideningOp::Signed));
          case uint32_t(GcOp::StructGetU):
            CHECK(emitStructGet(FieldWideningOp::Unsigned));
          case uint32_t(GcOp::ArrayNew):
            CHECK(emitArrayNew());
          case uint32_t(GcOp::ArrayNewDefault):
            CHECK(emitArrayNewDefault());
          case uint32_t(GcOp::ArrayNewFixed):
            CHECK(emitArrayNewFixed());
          case uint32_t(GcOp::ArrayNewData):
            CHECK(emitArrayNewData());
          case uint32_t(GcOp::ArrayNewElem):
            CHECK(emitArrayNewElem());
          case uint32_t(GcOp::ArrayInitData):
            CHECK(emitArrayInitData());
          case uint32_t(GcOp::ArrayInitElem):
            CHECK(emitArrayInitElem());
          case uint32_t(GcOp::ArraySet):
            CHECK(emitArraySet());
          case uint32_t(GcOp::ArrayGet):
            CHECK(emitArrayGet(FieldWideningOp::None));
          case uint32_t(GcOp::ArrayGetS):
            CHECK(emitArrayGet(FieldWideningOp::Signed));
          case uint32_t(GcOp::ArrayGetU):
            CHECK(emitArrayGet(FieldWideningOp::Unsigned));
          case uint32_t(GcOp::ArrayLen):
            CHECK(emitArrayLen());
          case uint32_t(GcOp::ArrayCopy):
            CHECK(emitArrayCopy());
          case uint32_t(GcOp::ArrayFill):
            CHECK(emitArrayFill());
          case uint32_t(GcOp::RefI31):
            CHECK(emitRefI31());
          case uint32_t(GcOp::I31GetS):
            CHECK(emitI31Get(FieldWideningOp::Signed));
          case uint32_t(GcOp::I31GetU):
            CHECK(emitI31Get(FieldWideningOp::Unsigned));
          case uint32_t(GcOp::BrOnCast):
            CHECK(emitBrOnCast(/*onSuccess=*/true));
          case uint32_t(GcOp::BrOnCastFail):
            CHECK(emitBrOnCast(/*onSuccess=*/false));
          case uint32_t(GcOp::RefTest):
            CHECK(emitRefTest(/*nullable=*/false));
          case uint32_t(GcOp::RefTestNull):
            CHECK(emitRefTest(/*nullable=*/true));
          case uint32_t(GcOp::RefCast):
            CHECK(emitRefCast(/*nullable=*/false));
          case uint32_t(GcOp::RefCastNull):
            CHECK(emitRefCast(/*nullable=*/true));
          case uint16_t(GcOp::AnyConvertExtern):
            CHECK(emitAnyConvertExtern());
          case uint16_t(GcOp::ExternConvertAny):
            CHECK(emitExternConvertAny());
          default:
            return iter().unrecognizedOpcode(&op);
        }  // switch (op.b1)
        break;
      }

      // SIMD operations
#ifdef ENABLE_WASM_SIMD
      case uint16_t(Op::SimdPrefix): {
        if (!codeMeta().simdAvailable()) {
          return iter().unrecognizedOpcode(&op);
        }
        switch (op.b1) {
          case uint32_t(SimdOp::V128Const):
            CHECK(emitConstSimd128());
          case uint32_t(SimdOp::V128Load):
            CHECK(emitLoad(ValType::V128, Scalar::Simd128));
          case uint32_t(SimdOp::V128Store):
            CHECK(emitStore(ValType::V128, Scalar::Simd128));
          case uint32_t(SimdOp::V128And):
          case uint32_t(SimdOp::V128Or):
          case uint32_t(SimdOp::V128Xor):
          case uint32_t(SimdOp::I8x16AvgrU):
          case uint32_t(SimdOp::I16x8AvgrU):
          case uint32_t(SimdOp::I8x16Add):
          case uint32_t(SimdOp::I8x16AddSatS):
          case uint32_t(SimdOp::I8x16AddSatU):
          case uint32_t(SimdOp::I8x16MinS):
          case uint32_t(SimdOp::I8x16MinU):
          case uint32_t(SimdOp::I8x16MaxS):
          case uint32_t(SimdOp::I8x16MaxU):
          case uint32_t(SimdOp::I16x8Add):
          case uint32_t(SimdOp::I16x8AddSatS):
          case uint32_t(SimdOp::I16x8AddSatU):
          case uint32_t(SimdOp::I16x8Mul):
          case uint32_t(SimdOp::I16x8MinS):
          case uint32_t(SimdOp::I16x8MinU):
          case uint32_t(SimdOp::I16x8MaxS):
          case uint32_t(SimdOp::I16x8MaxU):
          case uint32_t(SimdOp::I32x4Add):
          case uint32_t(SimdOp::I32x4Mul):
          case uint32_t(SimdOp::I32x4MinS):
          case uint32_t(SimdOp::I32x4MinU):
          case uint32_t(SimdOp::I32x4MaxS):
          case uint32_t(SimdOp::I32x4MaxU):
          case uint32_t(SimdOp::I64x2Add):
          case uint32_t(SimdOp::I64x2Mul):
          case uint32_t(SimdOp::F32x4Add):
          case uint32_t(SimdOp::F32x4Mul):
          case uint32_t(SimdOp::F32x4Min):
          case uint32_t(SimdOp::F32x4Max):
          case uint32_t(SimdOp::F64x2Add):
          case uint32_t(SimdOp::F64x2Mul):
          case uint32_t(SimdOp::F64x2Min):
          case uint32_t(SimdOp::F64x2Max):
          case uint32_t(SimdOp::I8x16Eq):
          case uint32_t(SimdOp::I8x16Ne):
          case uint32_t(SimdOp::I16x8Eq):
          case uint32_t(SimdOp::I16x8Ne):
          case uint32_t(SimdOp::I32x4Eq):
          case uint32_t(SimdOp::I32x4Ne):
          case uint32_t(SimdOp::I64x2Eq):
          case uint32_t(SimdOp::I64x2Ne):
          case uint32_t(SimdOp::F32x4Eq):
          case uint32_t(SimdOp::F32x4Ne):
          case uint32_t(SimdOp::F64x2Eq):
          case uint32_t(SimdOp::F64x2Ne):
          case uint32_t(SimdOp::I32x4DotI16x8S):
          case uint32_t(SimdOp::I16x8ExtmulLowI8x16S):
          case uint32_t(SimdOp::I16x8ExtmulHighI8x16S):
          case uint32_t(SimdOp::I16x8ExtmulLowI8x16U):
          case uint32_t(SimdOp::I16x8ExtmulHighI8x16U):
          case uint32_t(SimdOp::I32x4ExtmulLowI16x8S):
          case uint32_t(SimdOp::I32x4ExtmulHighI16x8S):
          case uint32_t(SimdOp::I32x4ExtmulLowI16x8U):
          case uint32_t(SimdOp::I32x4ExtmulHighI16x8U):
          case uint32_t(SimdOp::I64x2ExtmulLowI32x4S):
          case uint32_t(SimdOp::I64x2ExtmulHighI32x4S):
          case uint32_t(SimdOp::I64x2ExtmulLowI32x4U):
          case uint32_t(SimdOp::I64x2ExtmulHighI32x4U):
          case uint32_t(SimdOp::I16x8Q15MulrSatS):
            CHECK(emitBinarySimd128(/* commutative= */ true, SimdOp(op.b1)));
          case uint32_t(SimdOp::V128AndNot):
          case uint32_t(SimdOp::I8x16Sub):
          case uint32_t(SimdOp::I8x16SubSatS):
          case uint32_t(SimdOp::I8x16SubSatU):
          case uint32_t(SimdOp::I16x8Sub):
          case uint32_t(SimdOp::I16x8SubSatS):
          case uint32_t(SimdOp::I16x8SubSatU):
          case uint32_t(SimdOp::I32x4Sub):
          case uint32_t(SimdOp::I64x2Sub):
          case uint32_t(SimdOp::F32x4Sub):
          case uint32_t(SimdOp::F32x4Div):
          case uint32_t(SimdOp::F64x2Sub):
          case uint32_t(SimdOp::F64x2Div):
          case uint32_t(SimdOp::I8x16NarrowI16x8S):
          case uint32_t(SimdOp::I8x16NarrowI16x8U):
          case uint32_t(SimdOp::I16x8NarrowI32x4S):
          case uint32_t(SimdOp::I16x8NarrowI32x4U):
          case uint32_t(SimdOp::I8x16LtS):
          case uint32_t(SimdOp::I8x16LtU):
          case uint32_t(SimdOp::I8x16GtS):
          case uint32_t(SimdOp::I8x16GtU):
          case uint32_t(SimdOp::I8x16LeS):
          case uint32_t(SimdOp::I8x16LeU):
          case uint32_t(SimdOp::I8x16GeS):
          case uint32_t(SimdOp::I8x16GeU):
          case uint32_t(SimdOp::I16x8LtS):
          case uint32_t(SimdOp::I16x8LtU):
          case uint32_t(SimdOp::I16x8GtS):
          case uint32_t(SimdOp::I16x8GtU):
          case uint32_t(SimdOp::I16x8LeS):
          case uint32_t(SimdOp::I16x8LeU):
          case uint32_t(SimdOp::I16x8GeS):
          case uint32_t(SimdOp::I16x8GeU):
          case uint32_t(SimdOp::I32x4LtS):
          case uint32_t(SimdOp::I32x4LtU):
          case uint32_t(SimdOp::I32x4GtS):
          case uint32_t(SimdOp::I32x4GtU):
          case uint32_t(SimdOp::I32x4LeS):
          case uint32_t(SimdOp::I32x4LeU):
          case uint32_t(SimdOp::I32x4GeS):
          case uint32_t(SimdOp::I32x4GeU):
          case uint32_t(SimdOp::I64x2LtS):
          case uint32_t(SimdOp::I64x2GtS):
          case uint32_t(SimdOp::I64x2LeS):
          case uint32_t(SimdOp::I64x2GeS):
          case uint32_t(SimdOp::F32x4Lt):
          case uint32_t(SimdOp::F32x4Gt):
          case uint32_t(SimdOp::F32x4Le):
          case uint32_t(SimdOp::F32x4Ge):
          case uint32_t(SimdOp::F64x2Lt):
          case uint32_t(SimdOp::F64x2Gt):
          case uint32_t(SimdOp::F64x2Le):
          case uint32_t(SimdOp::F64x2Ge):
          case uint32_t(SimdOp::I8x16Swizzle):
          case uint32_t(SimdOp::F32x4PMax):
          case uint32_t(SimdOp::F32x4PMin):
          case uint32_t(SimdOp::F64x2PMax):
          case uint32_t(SimdOp::F64x2PMin):
            CHECK(emitBinarySimd128(/* commutative= */ false, SimdOp(op.b1)));
          case uint32_t(SimdOp::I8x16Splat):
          case uint32_t(SimdOp::I16x8Splat):
          case uint32_t(SimdOp::I32x4Splat):
            CHECK(emitSplatSimd128(ValType::I32, SimdOp(op.b1)));
          case uint32_t(SimdOp::I64x2Splat):
            CHECK(emitSplatSimd128(ValType::I64, SimdOp(op.b1)));
          case uint32_t(SimdOp::F32x4Splat):
            CHECK(emitSplatSimd128(ValType::F32, SimdOp(op.b1)));
          case uint32_t(SimdOp::F64x2Splat):
            CHECK(emitSplatSimd128(ValType::F64, SimdOp(op.b1)));
          case uint32_t(SimdOp::I8x16Neg):
          case uint32_t(SimdOp::I16x8Neg):
          case uint32_t(SimdOp::I16x8ExtendLowI8x16S):
          case uint32_t(SimdOp::I16x8ExtendHighI8x16S):
          case uint32_t(SimdOp::I16x8ExtendLowI8x16U):
          case uint32_t(SimdOp::I16x8ExtendHighI8x16U):
          case uint32_t(SimdOp::I32x4Neg):
          case uint32_t(SimdOp::I32x4ExtendLowI16x8S):
          case uint32_t(SimdOp::I32x4ExtendHighI16x8S):
          case uint32_t(SimdOp::I32x4ExtendLowI16x8U):
          case uint32_t(SimdOp::I32x4ExtendHighI16x8U):
          case uint32_t(SimdOp::I32x4TruncSatF32x4S):
          case uint32_t(SimdOp::I32x4TruncSatF32x4U):
          case uint32_t(SimdOp::I64x2Neg):
          case uint32_t(SimdOp::I64x2ExtendLowI32x4S):
          case uint32_t(SimdOp::I64x2ExtendHighI32x4S):
          case uint32_t(SimdOp::I64x2ExtendLowI32x4U):
          case uint32_t(SimdOp::I64x2ExtendHighI32x4U):
          case uint32_t(SimdOp::F32x4Abs):
          case uint32_t(SimdOp::F32x4Neg):
          case uint32_t(SimdOp::F32x4Sqrt):
          case uint32_t(SimdOp::F32x4ConvertI32x4S):
          case uint32_t(SimdOp::F32x4ConvertI32x4U):
          case uint32_t(SimdOp::F64x2Abs):
          case uint32_t(SimdOp::F64x2Neg):
          case uint32_t(SimdOp::F64x2Sqrt):
          case uint32_t(SimdOp::V128Not):
          case uint32_t(SimdOp::I8x16Popcnt):
          case uint32_t(SimdOp::I8x16Abs):
          case uint32_t(SimdOp::I16x8Abs):
          case uint32_t(SimdOp::I32x4Abs):
          case uint32_t(SimdOp::I64x2Abs):
          case uint32_t(SimdOp::F32x4Ceil):
          case uint32_t(SimdOp::F32x4Floor):
          case uint32_t(SimdOp::F32x4Trunc):
          case uint32_t(SimdOp::F32x4Nearest):
          case uint32_t(SimdOp::F64x2Ceil):
          case uint32_t(SimdOp::F64x2Floor):
          case uint32_t(SimdOp::F64x2Trunc):
          case uint32_t(SimdOp::F64x2Nearest):
          case uint32_t(SimdOp::F32x4DemoteF64x2Zero):
          case uint32_t(SimdOp::F64x2PromoteLowF32x4):
          case uint32_t(SimdOp::F64x2ConvertLowI32x4S):
          case uint32_t(SimdOp::F64x2ConvertLowI32x4U):
          case uint32_t(SimdOp::I32x4TruncSatF64x2SZero):
          case uint32_t(SimdOp::I32x4TruncSatF64x2UZero):
          case uint32_t(SimdOp::I16x8ExtaddPairwiseI8x16S):
          case uint32_t(SimdOp::I16x8ExtaddPairwiseI8x16U):
          case uint32_t(SimdOp::I32x4ExtaddPairwiseI16x8S):
          case uint32_t(SimdOp::I32x4ExtaddPairwiseI16x8U):
            CHECK(emitUnarySimd128(SimdOp(op.b1)));
          case uint32_t(SimdOp::V128AnyTrue):
          case uint32_t(SimdOp::I8x16AllTrue):
          case uint32_t(SimdOp::I16x8AllTrue):
          case uint32_t(SimdOp::I32x4AllTrue):
          case uint32_t(SimdOp::I64x2AllTrue):
          case uint32_t(SimdOp::I8x16Bitmask):
          case uint32_t(SimdOp::I16x8Bitmask):
          case uint32_t(SimdOp::I32x4Bitmask):
          case uint32_t(SimdOp::I64x2Bitmask):
            CHECK(emitReduceSimd128(SimdOp(op.b1)));
          case uint32_t(SimdOp::I8x16Shl):
          case uint32_t(SimdOp::I8x16ShrS):
          case uint32_t(SimdOp::I8x16ShrU):
          case uint32_t(SimdOp::I16x8Shl):
          case uint32_t(SimdOp::I16x8ShrS):
          case uint32_t(SimdOp::I16x8ShrU):
          case uint32_t(SimdOp::I32x4Shl):
          case uint32_t(SimdOp::I32x4ShrS):
          case uint32_t(SimdOp::I32x4ShrU):
          case uint32_t(SimdOp::I64x2Shl):
          case uint32_t(SimdOp::I64x2ShrS):
          case uint32_t(SimdOp::I64x2ShrU):
            CHECK(emitShiftSimd128(SimdOp(op.b1)));
          case uint32_t(SimdOp::I8x16ExtractLaneS):
          case uint32_t(SimdOp::I8x16ExtractLaneU):
            CHECK(emitExtractLaneSimd128(ValType::I32, 16, SimdOp(op.b1)));
          case uint32_t(SimdOp::I16x8ExtractLaneS):
          case uint32_t(SimdOp::I16x8ExtractLaneU):
            CHECK(emitExtractLaneSimd128(ValType::I32, 8, SimdOp(op.b1)));
          case uint32_t(SimdOp::I32x4ExtractLane):
            CHECK(emitExtractLaneSimd128(ValType::I32, 4, SimdOp(op.b1)));
          case uint32_t(SimdOp::I64x2ExtractLane):
            CHECK(emitExtractLaneSimd128(ValType::I64, 2, SimdOp(op.b1)));
          case uint32_t(SimdOp::F32x4ExtractLane):
            CHECK(emitExtractLaneSimd128(ValType::F32, 4, SimdOp(op.b1)));
          case uint32_t(SimdOp::F64x2ExtractLane):
            CHECK(emitExtractLaneSimd128(ValType::F64, 2, SimdOp(op.b1)));
          case uint32_t(SimdOp::I8x16ReplaceLane):
            CHECK(emitReplaceLaneSimd128(ValType::I32, 16, SimdOp(op.b1)));
          case uint32_t(SimdOp::I16x8ReplaceLane):
            CHECK(emitReplaceLaneSimd128(ValType::I32, 8, SimdOp(op.b1)));
          case uint32_t(SimdOp::I32x4ReplaceLane):
            CHECK(emitReplaceLaneSimd128(ValType::I32, 4, SimdOp(op.b1)));
          case uint32_t(SimdOp::I64x2ReplaceLane):
            CHECK(emitReplaceLaneSimd128(ValType::I64, 2, SimdOp(op.b1)));
          case uint32_t(SimdOp::F32x4ReplaceLane):
            CHECK(emitReplaceLaneSimd128(ValType::F32, 4, SimdOp(op.b1)));
          case uint32_t(SimdOp::F64x2ReplaceLane):
            CHECK(emitReplaceLaneSimd128(ValType::F64, 2, SimdOp(op.b1)));
          case uint32_t(SimdOp::V128Bitselect):
            CHECK(emitTernarySimd128(SimdOp(op.b1)));
          case uint32_t(SimdOp::I8x16Shuffle):
            CHECK(emitShuffleSimd128());
          case uint32_t(SimdOp::V128Load8Splat):
            CHECK(emitLoadSplatSimd128(Scalar::Uint8, SimdOp::I8x16Splat));
          case uint32_t(SimdOp::V128Load16Splat):
            CHECK(emitLoadSplatSimd128(Scalar::Uint16, SimdOp::I16x8Splat));
          case uint32_t(SimdOp::V128Load32Splat):
            CHECK(emitLoadSplatSimd128(Scalar::Float32, SimdOp::I32x4Splat));
          case uint32_t(SimdOp::V128Load64Splat):
            CHECK(emitLoadSplatSimd128(Scalar::Float64, SimdOp::I64x2Splat));
          case uint32_t(SimdOp::V128Load8x8S):
          case uint32_t(SimdOp::V128Load8x8U):
          case uint32_t(SimdOp::V128Load16x4S):
          case uint32_t(SimdOp::V128Load16x4U):
          case uint32_t(SimdOp::V128Load32x2S):
          case uint32_t(SimdOp::V128Load32x2U):
            CHECK(emitLoadExtendSimd128(SimdOp(op.b1)));
          case uint32_t(SimdOp::V128Load32Zero):
            CHECK(emitLoadZeroSimd128(Scalar::Float32, 4));
          case uint32_t(SimdOp::V128Load64Zero):
            CHECK(emitLoadZeroSimd128(Scalar::Float64, 8));
          case uint32_t(SimdOp::V128Load8Lane):
            CHECK(emitLoadLaneSimd128(1));
          case uint32_t(SimdOp::V128Load16Lane):
            CHECK(emitLoadLaneSimd128(2));
          case uint32_t(SimdOp::V128Load32Lane):
            CHECK(emitLoadLaneSimd128(4));
          case uint32_t(SimdOp::V128Load64Lane):
            CHECK(emitLoadLaneSimd128(8));
          case uint32_t(SimdOp::V128Store8Lane):
            CHECK(emitStoreLaneSimd128(1));
          case uint32_t(SimdOp::V128Store16Lane):
            CHECK(emitStoreLaneSimd128(2));
          case uint32_t(SimdOp::V128Store32Lane):
            CHECK(emitStoreLaneSimd128(4));
          case uint32_t(SimdOp::V128Store64Lane):
            CHECK(emitStoreLaneSimd128(8));
#  ifdef ENABLE_WASM_RELAXED_SIMD
          case uint32_t(SimdOp::F32x4RelaxedMadd):
          case uint32_t(SimdOp::F32x4RelaxedNmadd):
          case uint32_t(SimdOp::F64x2RelaxedMadd):
          case uint32_t(SimdOp::F64x2RelaxedNmadd):
          case uint32_t(SimdOp::I8x16RelaxedLaneSelect):
          case uint32_t(SimdOp::I16x8RelaxedLaneSelect):
          case uint32_t(SimdOp::I32x4RelaxedLaneSelect):
          case uint32_t(SimdOp::I64x2RelaxedLaneSelect):
          case uint32_t(SimdOp::I32x4RelaxedDotI8x16I7x16AddS): {
            if (!codeMeta().v128RelaxedEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitTernarySimd128(SimdOp(op.b1)));
          }
          case uint32_t(SimdOp::F32x4RelaxedMin):
          case uint32_t(SimdOp::F32x4RelaxedMax):
          case uint32_t(SimdOp::F64x2RelaxedMin):
          case uint32_t(SimdOp::F64x2RelaxedMax): {
            if (!codeMeta().v128RelaxedEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            // These aren't really commutative, because at least on Intel, the
            // behaviour in the presence of NaNs depends on the order of the
            // operands.  And we need to have that ordering fixed, so that we
            // can produce the same results as baseline.  See bug 1946618.
            CHECK(emitBinarySimd128(/* commutative= */ false, SimdOp(op.b1)));
          }
          case uint32_t(SimdOp::I16x8RelaxedQ15MulrS): {
            if (!codeMeta().v128RelaxedEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitBinarySimd128(/* commutative= */ true, SimdOp(op.b1)));
          }
          case uint32_t(SimdOp::I32x4RelaxedTruncF32x4S):
          case uint32_t(SimdOp::I32x4RelaxedTruncF32x4U):
          case uint32_t(SimdOp::I32x4RelaxedTruncF64x2SZero):
          case uint32_t(SimdOp::I32x4RelaxedTruncF64x2UZero): {
            if (!codeMeta().v128RelaxedEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitUnarySimd128(SimdOp(op.b1)));
          }
          case uint32_t(SimdOp::I8x16RelaxedSwizzle):
          case uint32_t(SimdOp::I16x8RelaxedDotI8x16I7x16S): {
            if (!codeMeta().v128RelaxedEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitBinarySimd128(/* commutative= */ false, SimdOp(op.b1)));
          }
#  endif

          default:
            return iter().unrecognizedOpcode(&op);
        }  // switch (op.b1)
        break;
      }
#endif

      // Miscellaneous operations
      case uint16_t(Op::MiscPrefix): {
        switch (op.b1) {
          case uint32_t(MiscOp::I32TruncSatF32S):
          case uint32_t(MiscOp::I32TruncSatF32U):
            CHECK(emitTruncate(ValType::F32, ValType::I32,
                               MiscOp(op.b1) == MiscOp::I32TruncSatF32U, true));
          case uint32_t(MiscOp::I32TruncSatF64S):
          case uint32_t(MiscOp::I32TruncSatF64U):
            CHECK(emitTruncate(ValType::F64, ValType::I32,
                               MiscOp(op.b1) == MiscOp::I32TruncSatF64U, true));
          case uint32_t(MiscOp::I64TruncSatF32S):
          case uint32_t(MiscOp::I64TruncSatF32U):
            CHECK(emitTruncate(ValType::F32, ValType::I64,
                               MiscOp(op.b1) == MiscOp::I64TruncSatF32U, true));
          case uint32_t(MiscOp::I64TruncSatF64S):
          case uint32_t(MiscOp::I64TruncSatF64U):
            CHECK(emitTruncate(ValType::F64, ValType::I64,
                               MiscOp(op.b1) == MiscOp::I64TruncSatF64U, true));
          case uint32_t(MiscOp::MemoryCopy):
            CHECK(emitMemCopy());
          case uint32_t(MiscOp::DataDrop):
            CHECK(emitDataOrElemDrop(/*isData=*/true));
          case uint32_t(MiscOp::MemoryFill):
            CHECK(emitMemFill());
          case uint32_t(MiscOp::MemoryInit):
            CHECK(emitMemInit());
          case uint32_t(MiscOp::TableCopy):
            CHECK(emitTableCopy());
          case uint32_t(MiscOp::ElemDrop):
            CHECK(emitDataOrElemDrop(/*isData=*/false));
          case uint32_t(MiscOp::TableInit):
            CHECK(emitTableInit());
          case uint32_t(MiscOp::TableFill):
            CHECK(emitTableFill());
#if ENABLE_WASM_MEMORY_CONTROL
          case uint32_t(MiscOp::MemoryDiscard): {
            if (!codeMeta().memoryControlEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitMemDiscard());
          }
#endif
          case uint32_t(MiscOp::TableGrow):
            CHECK(emitTableGrow());
          case uint32_t(MiscOp::TableSize):
            CHECK(emitTableSize());

          case uint32_t(MiscOp::I64Add128):
            if (!codeMeta().wideArithmeticEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitI64AddSub128(/*isAdd=*/true));
          case uint32_t(MiscOp::I64Sub128):
            if (!codeMeta().wideArithmeticEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitI64AddSub128(/*isAdd=*/false));
          case uint32_t(MiscOp::I64MulWideS):
            if (!codeMeta().wideArithmeticEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitI64MulWide(/*isSigned=*/true));
          case uint32_t(MiscOp::I64MulWideU):
            if (!codeMeta().wideArithmeticEnabled()) {
              return iter().unrecognizedOpcode(&op);
            }
            CHECK(emitI64MulWide(/*isSigned=*/false));

          default:
            return iter().unrecognizedOpcode(&op);
        }
        break;
      }

      // Thread operations
      case uint16_t(Op::ThreadPrefix): {
        // Though thread ops can be used on nonshared memories, we make them
        // unavailable if shared memory has been disabled in the prefs, for
        // maximum predictability and safety and consistency with JS.
        if (codeMeta().sharedMemoryEnabled() == Shareable::False) {
          return iter().unrecognizedOpcode(&op);
        }
        switch (op.b1) {
          case uint32_t(ThreadOp::Notify):
            CHECK(emitNotify());

          case uint32_t(ThreadOp::I32Wait):
            CHECK(emitWait(ValType::I32, 4));
          case uint32_t(ThreadOp::I64Wait):
            CHECK(emitWait(ValType::I64, 8));
          case uint32_t(ThreadOp::Fence):
            CHECK(emitFence());

          case uint32_t(ThreadOp::I32AtomicLoad):
            CHECK(emitAtomicLoad(ValType::I32, Scalar::Int32));
          case uint32_t(ThreadOp::I64AtomicLoad):
            CHECK(emitAtomicLoad(ValType::I64, Scalar::Int64));
          case uint32_t(ThreadOp::I32AtomicLoad8U):
            CHECK(emitAtomicLoad(ValType::I32, Scalar::Uint8));
          case uint32_t(ThreadOp::I32AtomicLoad16U):
            CHECK(emitAtomicLoad(ValType::I32, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicLoad8U):
            CHECK(emitAtomicLoad(ValType::I64, Scalar::Uint8));
          case uint32_t(ThreadOp::I64AtomicLoad16U):
            CHECK(emitAtomicLoad(ValType::I64, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicLoad32U):
            CHECK(emitAtomicLoad(ValType::I64, Scalar::Uint32));

          case uint32_t(ThreadOp::I32AtomicStore):
            CHECK(emitAtomicStore(ValType::I32, Scalar::Int32));
          case uint32_t(ThreadOp::I64AtomicStore):
            CHECK(emitAtomicStore(ValType::I64, Scalar::Int64));
          case uint32_t(ThreadOp::I32AtomicStore8U):
            CHECK(emitAtomicStore(ValType::I32, Scalar::Uint8));
          case uint32_t(ThreadOp::I32AtomicStore16U):
            CHECK(emitAtomicStore(ValType::I32, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicStore8U):
            CHECK(emitAtomicStore(ValType::I64, Scalar::Uint8));
          case uint32_t(ThreadOp::I64AtomicStore16U):
            CHECK(emitAtomicStore(ValType::I64, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicStore32U):
            CHECK(emitAtomicStore(ValType::I64, Scalar::Uint32));

          case uint32_t(ThreadOp::I32AtomicAdd):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Add));
          case uint32_t(ThreadOp::I64AtomicAdd):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Add));
          case uint32_t(ThreadOp::I32AtomicAdd8U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Add));
          case uint32_t(ThreadOp::I32AtomicAdd16U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Add));
          case uint32_t(ThreadOp::I64AtomicAdd8U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Add));
          case uint32_t(ThreadOp::I64AtomicAdd16U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Add));
          case uint32_t(ThreadOp::I64AtomicAdd32U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Add));

          case uint32_t(ThreadOp::I32AtomicSub):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Sub));
          case uint32_t(ThreadOp::I64AtomicSub):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Sub));
          case uint32_t(ThreadOp::I32AtomicSub8U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Sub));
          case uint32_t(ThreadOp::I32AtomicSub16U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Sub));
          case uint32_t(ThreadOp::I64AtomicSub8U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Sub));
          case uint32_t(ThreadOp::I64AtomicSub16U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Sub));
          case uint32_t(ThreadOp::I64AtomicSub32U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Sub));

          case uint32_t(ThreadOp::I32AtomicAnd):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::And));
          case uint32_t(ThreadOp::I64AtomicAnd):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::And));
          case uint32_t(ThreadOp::I32AtomicAnd8U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::And));
          case uint32_t(ThreadOp::I32AtomicAnd16U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::And));
          case uint32_t(ThreadOp::I64AtomicAnd8U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::And));
          case uint32_t(ThreadOp::I64AtomicAnd16U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::And));
          case uint32_t(ThreadOp::I64AtomicAnd32U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::And));

          case uint32_t(ThreadOp::I32AtomicOr):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Or));
          case uint32_t(ThreadOp::I64AtomicOr):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Or));
          case uint32_t(ThreadOp::I32AtomicOr8U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Or));
          case uint32_t(ThreadOp::I32AtomicOr16U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Or));
          case uint32_t(ThreadOp::I64AtomicOr8U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Or));
          case uint32_t(ThreadOp::I64AtomicOr16U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Or));
          case uint32_t(ThreadOp::I64AtomicOr32U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Or));

          case uint32_t(ThreadOp::I32AtomicXor):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Xor));
          case uint32_t(ThreadOp::I64AtomicXor):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Xor));
          case uint32_t(ThreadOp::I32AtomicXor8U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Xor));
          case uint32_t(ThreadOp::I32AtomicXor16U):
            CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Xor));
          case uint32_t(ThreadOp::I64AtomicXor8U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Xor));
          case uint32_t(ThreadOp::I64AtomicXor16U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Xor));
          case uint32_t(ThreadOp::I64AtomicXor32U):
            CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Xor));

          case uint32_t(ThreadOp::I32AtomicXchg):
            CHECK(emitAtomicXchg(ValType::I32, Scalar::Int32));
          case uint32_t(ThreadOp::I64AtomicXchg):
            CHECK(emitAtomicXchg(ValType::I64, Scalar::Int64));
          case uint32_t(ThreadOp::I32AtomicXchg8U):
            CHECK(emitAtomicXchg(ValType::I32, Scalar::Uint8));
          case uint32_t(ThreadOp::I32AtomicXchg16U):
            CHECK(emitAtomicXchg(ValType::I32, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicXchg8U):
            CHECK(emitAtomicXchg(ValType::I64, Scalar::Uint8));
          case uint32_t(ThreadOp::I64AtomicXchg16U):
            CHECK(emitAtomicXchg(ValType::I64, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicXchg32U):
            CHECK(emitAtomicXchg(ValType::I64, Scalar::Uint32));

          case uint32_t(ThreadOp::I32AtomicCmpXchg):
            CHECK(emitAtomicCmpXchg(ValType::I32, Scalar::Int32));
          case uint32_t(ThreadOp::I64AtomicCmpXchg):
            CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Int64));
          case uint32_t(ThreadOp::I32AtomicCmpXchg8U):
            CHECK(emitAtomicCmpXchg(ValType::I32, Scalar::Uint8));
          case uint32_t(ThreadOp::I32AtomicCmpXchg16U):
            CHECK(emitAtomicCmpXchg(ValType::I32, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicCmpXchg8U):
            CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Uint8));
          case uint32_t(ThreadOp::I64AtomicCmpXchg16U):
            CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Uint16));
          case uint32_t(ThreadOp::I64AtomicCmpXchg32U):
            CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Uint32));

          default:
            return iter().unrecognizedOpcode(&op);
        }
        break;
      }

      case uint16_t(Op::MozPrefix): {
        if (!codeMeta().isBuiltinModule()) {
          return iter().unrecognizedOpcode(&op);
        }

        switch (op.b1) {
          case uint32_t(MozOp::CallBuiltinModuleFunc):
            CHECK(emitCallBuiltinModuleFunc());
#ifdef ENABLE_WASM_JSPI
          case uint32_t(MozOp::GuardSuspending):
            CHECK(emitGuardSuspending());
#endif  // ENABLE_WASM_JSPI

          default:
            return iter().unrecognizedOpcode(&op);
        }
        break;
      }

      default:
        return iter().unrecognizedOpcode(&op);
    }
  }

  MOZ_CRASH("unreachable");

#undef CHECK
}

}  // end anonymous namespace

bool RootCompiler::generate() {
  // Only activate branch hinting if the option is enabled and some hints were
  // parsed.
  if (codeMeta_.branchHintingEnabled() && !codeMeta_.branchHints.isEmpty()) {
    compileInfo_.setBranchHinting(true);
  }

  // Figure out what the inlining budget for this function is.  If we've
  // already exceeded the module-level limit, the budget is zero.  See
  // "[SMDOC] Per-function and per-module inlining limits" (WasmHeuristics.h)
  if (codeTailMeta_) {
    auto guard = codeTailMeta_->inliningBudget.lock();

    if (guard.get() > 0) {
      localInliningBudget_ =
          int64_t(codeMeta_.codeSectionSize()) * PerFunctionMaxInliningRatio;
      localInliningBudget_ =
          std::min<int64_t>(localInliningBudget_, guard.get());
    } else {
      localInliningBudget_ = 0;
    }
    MOZ_ASSERT(localInliningBudget_ >= 0);
  } else {
    localInliningBudget_ = 0;
  }

  // Build the MIR graph
  FunctionCompiler funcCompiler(*this, decoder_, func_, locals_, compileInfo_);
  if (!funcCompiler.initRoot() || !funcCompiler.startBlock() ||
      !funcCompiler.emitBodyExprs()) {
    return false;
  }
  funcCompiler.finish();
  observedFeatures_ = funcCompiler.featureUsage();

  MOZ_ASSERT(loopDepth_ == 0);

  funcStats_.numFuncs += 1;
  funcStats_.bytecodeSize += func_.bytecodeSize();
  funcStats_.inlinedDirectCallCount += inliningStats_.inlinedDirectFunctions;
  funcStats_.inlinedCallRefCount += inliningStats_.inlinedCallRefFunctions;
  funcStats_.inlinedDirectCallBytecodeSize +=
      inliningStats_.inlinedDirectBytecodeSize;
  funcStats_.inlinedCallRefBytecodeSize +=
      inliningStats_.inlinedCallRefBytecodeSize;
  funcStats_.numLargeFunctionBackoffs +=
      inliningStats_.largeFunctionBackoff ? 1 : 0;

  if (codeTailMeta_) {
    auto guard = codeTailMeta_->inliningBudget.lock();
    // Update the module's inlining budget accordingly.  If it is already
    // negative, no more inlining for the module can happen, so there's no
    // point in updating it further.
    if (guard.get() >= 0) {
      guard.get() -= int64_t(inliningStats_.inlinedDirectBytecodeSize);
      guard.get() -= int64_t(inliningStats_.inlinedCallRefBytecodeSize);
      if (guard.get() < 0) {
        JS_LOG(wasmPerf, Info,
               "CM=..%06lx  RC::generate            "
               "Inlining budget for entire module exceeded",
               0xFFFFFF & (unsigned long)uintptr_t(&codeMeta_));
      }
    }
    // If this particular root function overran the function-level
    // limit, note that in the module too.
    if (localInliningBudget_ < 0) {
      funcStats_.numInliningBudgetOverruns += 1;
    }
  }

  return true;
}

CompileInfo* RootCompiler::startInlineCall(
    uint32_t callerFuncIndex, BytecodeOffset callerOffset,
    uint32_t calleeFuncIndex, uint32_t numLocals, size_t inlineeBytecodeSize,
    InliningHeuristics::CallKind callKind) {
  if (callKind == InliningHeuristics::CallKind::Direct) {
    inliningStats_.inlinedDirectBytecodeSize += inlineeBytecodeSize;
    inliningStats_.inlinedDirectFunctions += 1;
  } else {
    MOZ_ASSERT(callKind == InliningHeuristics::CallKind::CallRef);
    inliningStats_.inlinedCallRefBytecodeSize += inlineeBytecodeSize;
    inliningStats_.inlinedCallRefFunctions += 1;
  }

  // Update the inlining budget accordingly.  If it is already negative, no
  // more inlining within this root function can happen, so there's no
  // point in updating it further.
  if (localInliningBudget_ >= 0) {
    localInliningBudget_ -= int64_t(inlineeBytecodeSize);
#ifdef JS_JITSPEW
    if (localInliningBudget_ <= 0) {
      JS_LOG(wasmPerf, Info,
             "CM=..%06lx  RC::startInlineCall     "
             "Inlining budget for fI=%u exceeded",
             0xFFFFFF & (unsigned long)uintptr_t(&codeMeta_), callerFuncIndex);
    }
#endif
  }

  // Add the callers offset to the stack of inlined caller offsets
  if (!inlinedCallerOffsets_.append(callerOffset)) {
    return nullptr;
  }

  // Cache a copy of the current stack of inlined caller offsets that can be
  // shared across all call sites
  InlinedCallerOffsets inlinedCallerOffsets;
  if (!inlinedCallerOffsets.appendAll(inlinedCallerOffsets_)) {
    return nullptr;
  }

  if (!inliningContext_.append(std::move(inlinedCallerOffsets),
                               &inlinedCallerOffsetsIndex_)) {
    return nullptr;
  }

  UniqueCompileInfo compileInfo = MakeUnique<CompileInfo>(numLocals);
  if (!compileInfo || !compileInfos_.append(std::move(compileInfo))) {
    return nullptr;
  }
  return compileInfos_[compileInfos_.length() - 1].get();
}

void RootCompiler::finishInlineCall() { inlinedCallerOffsets_.popBack(); }

bool wasm::IonCompileFunctions(const CodeMetadata& codeMeta,
                               const CodeTailMetadata* codeTailMeta,
                               const CompilerEnvironment& compilerEnv,
                               LifoAlloc& lifo,
                               const FuncCompileInputVector& inputs,
                               CompiledCode* code, UniqueChars* error) {
  MOZ_ASSERT(compilerEnv.tier() == Tier::Optimized);
  MOZ_ASSERT(compilerEnv.debug() == DebugEnabled::False);
  MOZ_ASSERT_IF(compilerEnv.mode() == CompileMode::LazyTiering, !!codeTailMeta);

  // We should not interact with the GC heap, nor allocate from it when we are
  // compiling wasm code. Ion data structures have some fields for GC objects
  // that we do not use, yet can confuse the static analysis here. Disable it
  // for this function.
  JS::AutoSuppressGCAnalysis nogc;

  TempAllocator alloc(&lifo);
  JitContext jitContext;
  MOZ_ASSERT(IsCompilingWasm());
  WasmMacroAssembler masm(alloc);
#if defined(JS_CODEGEN_ARM64)
  masm.SetStackPointer64(PseudoStackPointer64);
#endif

  // Swap in already-allocated empty vectors to avoid malloc/free.
  MOZ_ASSERT(code->empty());
  if (!code->swap(masm)) {
    return false;
  }

  // Create a description of the stack layout created by GenerateTrapExit().
  RegisterOffsets trapExitLayout;
  size_t trapExitLayoutNumWords;
  GenerateTrapExitRegisterOffsets(&trapExitLayout, &trapExitLayoutNumWords);

  for (const FuncCompileInput& func : inputs) {
    JitSpew(JitSpew_Codegen, "\n");
    JitSpew(JitSpew_Codegen,
            "# ================================"
            "==================================");
    JitSpew(JitSpew_Codegen, "# ==");
    JitSpew(JitSpew_Codegen,
            "# wasm::IonCompileFunctions: starting on function index %d",
            (int)func.index);

    Decoder d(func.begin, func.end, func.bytecodeOffset, error);

    // Build the local types vector.
    ValTypeVector locals;
    if (!DecodeLocalEntriesWithParams(d, codeMeta, func.index, &locals)) {
      return false;
    }

    // Set up for Ion compilation.
    RootCompiler rootCompiler(compilerEnv, codeMeta, codeTailMeta, alloc,
                              locals, func, d, masm.tryNotes(),
                              masm.inliningContext());
    if (!rootCompiler.generate()) {
      return false;
    }

    // Record observed feature usage
    FeatureUsage observedFeatures = rootCompiler.observedFeatures();
    code->featureUsage |= observedFeatures;

    // Compile MIR graph
    {
      rootCompiler.mirGen().spewBeginWasmFunction(func.index);
      jit::AutoSpewEndFunction spewEndFunction(&rootCompiler.mirGen());

      if (!OptimizeMIR(&rootCompiler.mirGen())) {
        return false;
      }

      LIRGraph* lir = GenerateLIR(&rootCompiler.mirGen());
      if (!lir) {
        return false;
      }

      size_t unwindInfoBefore = masm.codeRangeUnwindInfos().length();

      CodeGenerator codegen(&rootCompiler.mirGen(), lir, &masm, &codeMeta);

      TrapSiteDesc prologueTrapSiteDesc(
          wasm::BytecodeOffset(func.bytecodeOffset));
      FuncOffsets offsets;
      ArgTypeVector args(codeMeta.getFuncType(func.index));
      if (!codegen.generateWasm(CallIndirectId::forFunc(codeMeta, func.index),
                                prologueTrapSiteDesc, args, trapExitLayout,
                                trapExitLayoutNumWords, &offsets,
                                &code->stackMaps, &d)) {
        return false;
      }

      bool hasUnwindInfo =
          unwindInfoBefore != masm.codeRangeUnwindInfos().length();

      // Record this function's code range
      if (!code->codeRanges.emplaceBack(func.index, offsets, hasUnwindInfo)) {
        return false;
      }
    }

    if (PerfEnabled()) {
      IonPerfSpewer spewer = std::move(rootCompiler.mirGen().perfSpewer());
      if (!code->funcIonSpewers.emplaceBack(func.index, std::move(spewer))) {
        return false;
      }
    }

    // Record this function's compilation stats
    code->compileStats.merge(rootCompiler.funcStats());

    // Record this function's specific feature usage
    if (!code->funcs.emplaceBack(func.index, observedFeatures)) {
      return false;
    }

    JitSpew(JitSpew_Codegen,
            "# wasm::IonCompileFunctions: completed function index %d",
            (int)func.index);
    JitSpew(JitSpew_Codegen, "# ==");
    JitSpew(JitSpew_Codegen,
            "# ================================"
            "==================================");
    JitSpew(JitSpew_Codegen, "\n");
  }

  masm.finish();
  if (masm.oom()) {
    return false;
  }

  return code->swap(masm);
}

bool wasm::IonDumpFunction(const CompilerEnvironment& compilerEnv,
                           const CodeMetadata& codeMeta,
                           const FuncCompileInput& func, GenericPrinter& out,
                           UniqueChars* error) {
#ifdef JS_JITSPEW
  LifoAlloc lifo(TempAllocator::PreferredLifoChunkSize,
                 js::BackgroundMallocArena);
  TempAllocator alloc(&lifo);
  JitContext jitContext;
  Decoder d(func.begin, func.end, func.bytecodeOffset, error);

  // Decode the locals.
  ValTypeVector locals;
  if (!DecodeLocalEntriesWithParams(d, codeMeta, func.index, &locals)) {
    return false;
  }

  TryNoteVector tryNotes;
  InliningContext inliningContext;
  RootCompiler rootCompiler(compilerEnv, codeMeta, nullptr, alloc, locals, func,
                            d, tryNotes, inliningContext);
  MIRGenerator& mirGen = rootCompiler.mirGen();
  GraphSpewer graphSpewer(out, &codeMeta);

  graphSpewer.begin();
  mirGen.setGraphSpewer(&graphSpewer);
  mirGen.spewBeginWasmFunction(func.index);

  if (!rootCompiler.generate() || !OptimizeMIR(&mirGen) ||
      !GenerateLIR(&mirGen)) {
    return false;
  }

  mirGen.spewEndFunction();
  graphSpewer.end();
  return true;
#else
  UniqueChars errStr =
      DuplicateString("cannot dump Ion without --enable-jitspew");
  if (!errStr) {
    return false;
  }
  *error = std::move(errStr);
  return false;
#endif
}

bool js::wasm::IonPlatformSupport() {
#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) ||       \
    defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) ||    \
    defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_LOONG64) || \
    defined(JS_CODEGEN_RISCV64)
  return true;
#else
  return false;
#endif
}
