Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/js/src/wasm/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 347 kB image not shown  

Quelle  WasmIonCompile.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 *
 * 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 "mozilla/MathAlgorithms.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 "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/WasmStubs.h"
#include "wasm/WasmValidate.h"

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

using mozilla::IsPowerOfTwo;
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.
  WasmABIArgGenerator abi;

  // 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;

  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 rootBytecodeSize = 0;            // size of root function
  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
};

// 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 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_;
  // A copy of the above stack for efficient sharing.
  SharedBytecodeOffsetVector inlinedCallerOffsetsVector_;

  // 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 inliningBudget_;

  // 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_;

 public:
  RootCompiler(const CompilerEnvironment& compilerEnv,
               const CodeMetadata& codeMeta, TempAllocator& alloc,
               const ValTypeVector& locals, const FuncCompileInput& func,
               Decoder& decoder, wasm::TryNoteVector& tryNotes)
      : compilerEnv_(compilerEnv),
        codeMeta_(codeMeta),
        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)),
        loopDepth_(0),
        inliningBudget_(0),
        tryNotes_(tryNotes) {}

  const CompilerEnvironment& compilerEnv() const { return compilerEnv_; }
  const CodeMetadata& codeMeta() const { return codeMeta_; }
  TempAllocator& alloc() { return alloc_; }
  MIRGraph& mirGraph() { return mirGraph_; }
  MIRGenerator& mirGen() { return mirGen_; }
  int64_t inliningBudget() const { return inliningBudget_; }
  FeatureUsage observedFeatures() const { return observedFeatures_; }

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

  [[nodiscard]] bool generate();

  const ShareableBytecodeOffsetVector* inlinedCallerOffsets() {
    return inlinedCallerOffsetsVector_.get();
  }

  // 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 lastReadCallSite_;
  size_t numCallRefs_;

  // 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),
        lastReadCallSite_(0),
        numCallRefs_(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),
        lastReadCallSite_(0),
        numCallRefs_(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(); }

  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(); }
  TrapSiteDesc trapSiteDesc() {
    return TrapSiteDesc(wasm::BytecodeOffset(bytecodeOffset()),
                        rootCompiler_.inlinedCallerOffsets());
  }
  TrapSiteDesc trapSiteDescWithCallSiteLineNumber() {
    return TrapSiteDesc(wasm::BytecodeOffset(readCallSiteLineOrBytecode()),
                        rootCompiler_.inlinedCallerOffsets());
  }
  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 ArgTypeVector args(funcType());

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

    for (WasmABIArgIter i(args); !i.done(); i++) {
      MWasmParameter* ins = MWasmParameter::New(alloc(), *i, i.mirType());
      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(func_.callSiteLineNums.length() == lastReadCallSite_);
    MOZ_ASSERT_IF(
        compilerEnv().mode() == CompileMode::LazyTiering,
        codeMeta().getFuncDefCallRefs(funcIndex()).length == numCallRefs_);
    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::New(alloc(), Int32Value(i), MIRType::Int32);
    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() {
    if (inDeadCode()) {
      return nullptr;
    }
    // MConstant has a lot of baggage so we don't use that here.
    MWasmNullConstant* constant = MWasmNullConstant::New(alloc());
    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();
      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) && !codeMeta().isAsmJS();
  }

  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;
    }
    bool trapOnError = !codeMeta().isAsmJS();
    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, trapSiteDesc());
      curBlock_->add(ins);
      return ins;
    }
#endif

    auto* ins = MDiv::New(alloc(), lhs, rhs, type, unsignd, trapOnError,
                          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;
    }
    bool trapOnError = !codeMeta().isAsmJS();
    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, 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,
                          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* 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();
    if (!nullVal) {
      return nullptr;
    }
    return compare(ref, nullVal, compareOp, MCompare::Compare_WasmAnyRef);
  }

  [[nodiscard]] bool refAsNonNull(MDefinition* ref) {
    if (inDeadCode()) {
      return true;
    }

    auto* ins = MWasmTrapIfNull::New(
        alloc(), ref, wasm::Trap::NullPointerDereference, trapSiteDesc());

    curBlock_->add(ins);
    return true;
  }

  [[nodiscard]] bool brOnNull(uint32_t relativeDepth, const DefVector& values,
                              const ResultType& type, MDefinition* condition) {
    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)) {
      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) {
    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)) {
      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);

    // asm.js accesses are always aligned and need no checks.
    if (codeMeta().isAsmJS() || !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) {
    uint64_t offsetGuardLimit = GetMaxOffsetGuardLimit(
        codeMeta().hugeMemoryEnabled(access->memoryIndex()));

    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()));

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

  MWasmLoadInstance* needBoundsCheck(uint32_t memoryIndex) {
#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.
    static_assert(0x100000000 % PageSize == 0);
    bool mem32LimitIs64Bits =
        isMem32(memoryIndex) &&
        !codeMeta().memories[memoryIndex].boundsCheckLimitIs32Bits() &&
        MaxMemoryPages(codeMeta().memories[memoryIndex].addressType()) >=
            Pages(0x100000000 / PageSize);
#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 target = memoryIndex == 0 ? MWasmBoundsCheck::Memory0
                                   : MWasmBoundsCheck::Unknown;
    auto* ins = MWasmBoundsCheck::New(alloc(), actualBase, boundsCheckLimit,
                                      trapSiteDesc(), target);
    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());
    MOZ_ASSERT(!codeMeta().isAsmJS());

    // 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());
    MInstruction* load = nullptr;
    if (codeMeta().isAsmJS()) {
      MOZ_ASSERT(access->offset64() == 0);
      MWasmLoadInstance* boundsCheckLimit =
          maybeLoadBoundsCheckLimit(access->memoryIndex(), MIRType::Int32);
      load = MAsmJSLoadHeap::New(alloc(), memoryBase, base, boundsCheckLimit,
                                 access->type());
    } else {
      checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
      MOZ_ASSERT(base->type() == MIRType::Int32);
#endif
      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());
    MInstruction* store = nullptr;
    if (codeMeta().isAsmJS()) {
      MOZ_ASSERT(access->offset64() == 0);
      MWasmLoadInstance* boundsCheckLimit =
          maybeLoadBoundsCheckLimit(access->memoryIndex(), MIRType::Int32);
      store = MAsmJSStoreHeap::New(alloc(), memoryBase, base, boundsCheckLimit,
                                   access->type(), v);
    } else {
      checkOffsetAndAlignmentAndBounds(access, &base);
#ifndef JS_64BIT
      MOZ_ASSERT(base->type() == MIRType::Int32);
#endif
      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;
    MOZ_ASSERT(!codeMeta().isAsmJS());
    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;
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=89 H=96 G=92

¤ Dauer der Verarbeitung: 0.60 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.