summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmIonCompile.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--js/src/wasm/WasmIonCompile.cpp8691
1 files changed, 8691 insertions, 0 deletions
diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp
new file mode 100644
index 0000000000..2f5e3f7233
--- /dev/null
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -0,0 +1,8691 @@
+/* -*- 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.h"
+#include "jit/ShuffleAnalysis.h"
+#include "js/ScalarType.h" // js::Scalar::Type
+#include "wasm/WasmBaselineCompile.h"
+#include "wasm/WasmBuiltins.h"
+#include "wasm/WasmCodegenTypes.h"
+#include "wasm/WasmGC.h"
+#include "wasm/WasmGcObject.h"
+#include "wasm/WasmGenerator.h"
+#include "wasm/WasmIntrinsic.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::Maybe;
+using mozilla::Nothing;
+using mozilla::Some;
+
+namespace {
+
+using BlockVector = Vector<MBasicBlock*, 8, SystemAllocPolicy>;
+using DefVector = Vector<MDefinition*, 8, SystemAllocPolicy>;
+
+// To compile try-catch blocks, we extend the IonCompilePolicy's ControlItem
+// from being just an MBasicBlock* to a Control structure collecting additional
+// information.
+using ControlInstructionVector =
+ Vector<MControlInstruction*, 8, SystemAllocPolicy>;
+
+struct Control {
+ MBasicBlock* block;
+ // For a try-catch ControlItem, when its block's Labelkind is Try, this
+ // collects branches to later bind and create the try's landing pad.
+ ControlInstructionVector tryPadPatches;
+
+ Control() : block(nullptr) {}
+
+ explicit Control(MBasicBlock* block) : block(block) {}
+
+ public:
+ void setBlock(MBasicBlock* newBlock) { block = newBlock; }
+};
+
+// [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 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>;
+
+class FunctionCompiler;
+
+// CallCompileState describes a call that is being compiled.
+
+class 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;
+
+ // Only FunctionCompiler should be directly manipulating CallCompileState.
+ friend class FunctionCompiler;
+};
+
+// Encapsulates the compilation of a single function in an asm.js module. The
+// function compiler handles the creation and final backend compilation of the
+// MIR graph.
+class FunctionCompiler {
+ struct ControlFlowPatch {
+ MControlInstruction* ins;
+ uint32_t index;
+ ControlFlowPatch(MControlInstruction* ins, uint32_t index)
+ : ins(ins), index(index) {}
+ };
+
+ using ControlFlowPatchVector = Vector<ControlFlowPatch, 0, SystemAllocPolicy>;
+ using ControlFlowPatchVectorVector =
+ Vector<ControlFlowPatchVector, 0, SystemAllocPolicy>;
+
+ const ModuleEnvironment& moduleEnv_;
+ IonOpIter iter_;
+ const FuncCompileInput& func_;
+ const ValTypeVector& locals_;
+ size_t lastReadCallSite_;
+
+ TempAllocator& alloc_;
+ MIRGraph& graph_;
+ const CompileInfo& info_;
+ MIRGenerator& mirGen_;
+
+ MBasicBlock* curBlock_;
+ uint32_t maxStackArgBytes_;
+
+ uint32_t loopDepth_;
+ uint32_t blockDepth_;
+ ControlFlowPatchVectorVector blockPatches_;
+
+ // Instance pointer argument to the current function.
+ MWasmParameter* instancePointer_;
+ MWasmParameter* stackResultPointer_;
+
+ // Reference to masm.tryNotes_
+ wasm::TryNoteVector& tryNotes_;
+
+ public:
+ FunctionCompiler(const ModuleEnvironment& moduleEnv, Decoder& decoder,
+ const FuncCompileInput& func, const ValTypeVector& locals,
+ MIRGenerator& mirGen, TryNoteVector& tryNotes)
+ : moduleEnv_(moduleEnv),
+ iter_(moduleEnv, decoder),
+ func_(func),
+ locals_(locals),
+ lastReadCallSite_(0),
+ alloc_(mirGen.alloc()),
+ graph_(mirGen.graph()),
+ info_(mirGen.outerInfo()),
+ mirGen_(mirGen),
+ curBlock_(nullptr),
+ maxStackArgBytes_(0),
+ loopDepth_(0),
+ blockDepth_(0),
+ instancePointer_(nullptr),
+ stackResultPointer_(nullptr),
+ tryNotes_(tryNotes) {}
+
+ const ModuleEnvironment& moduleEnv() const { return moduleEnv_; }
+
+ IonOpIter& iter() { return iter_; }
+ TempAllocator& alloc() const { return alloc_; }
+ // FIXME(1401675): Replace with BlockType.
+ uint32_t funcIndex() const { return func_.index; }
+ const FuncType& funcType() const {
+ return *moduleEnv_.funcs[func_.index].type;
+ }
+
+ BytecodeOffset bytecodeOffset() const { return iter_.bytecodeOffset(); }
+ BytecodeOffset bytecodeIfNotAsmJS() const {
+ return moduleEnv_.isAsmJS() ? BytecodeOffset() : iter_.bytecodeOffset();
+ }
+
+ [[nodiscard]] bool init() {
+ // 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;
+ }
+
+ void finish() {
+ mirGen().initWasmMaxStackArgBytes(maxStackArgBytes_);
+
+ MOZ_ASSERT(loopDepth_ == 0);
+ MOZ_ASSERT(blockDepth_ == 0);
+#ifdef DEBUG
+ for (ControlFlowPatchVector& patches : blockPatches_) {
+ MOZ_ASSERT(patches.empty());
+ }
+#endif
+ MOZ_ASSERT(inDeadCode());
+ MOZ_ASSERT(done(), "all bytes must be consumed");
+ MOZ_ASSERT(func_.callSiteLineNums.length() == lastReadCallSite_);
+ }
+
+ /************************* Read-only interface (after local scope setup) */
+
+ MIRGenerator& mirGen() const { return mirGen_; }
+ MIRGraph& mirGraph() const { return graph_; }
+ const CompileInfo& info() const { return info_; }
+
+ 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) && !moduleEnv().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 = !moduleEnv().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, bytecodeOffset());
+ curBlock_->add(ins);
+ return ins;
+ }
+#endif
+
+ auto* ins = MDiv::New(alloc(), lhs, rhs, type, unsignd, trapOnError,
+ bytecodeOffset(), 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 = !moduleEnv().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, bytecodeOffset());
+ 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,
+ bytecodeOffset());
+ curBlock_->add(ins);
+ return ins;
+ }
+
+ MDefinition* bitnot(MDefinition* op) {
+ if (inDeadCode()) {
+ return nullptr;
+ }
+ auto* ins = MBitNot::New(alloc(), op);
+ 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, bytecodeOffset());
+ 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, bytecodeOffset());
+ 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* value, JSOp compareOp) {
+ MDefinition* nullVal = constantNullRef();
+ if (!nullVal) {
+ return nullptr;
+ }
+ return compare(value, nullVal, compareOp, MCompare::Compare_RefOrNull);
+ }
+
+ [[nodiscard]] bool refAsNonNull(MDefinition* value) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ auto* ins = MWasmTrapIfNull::New(
+ alloc(), value, wasm::Trap::NullPointerDereference, bytecodeOffset());
+
+ curBlock_->add(ins);
+ return true;
+ }
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ [[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;
+ }
+
+#endif // ENABLE_WASM_FUNCTION_REFERENCES
+
+#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.
+ MWasmLoadInstance* maybeLoadMemoryBase() {
+ MWasmLoadInstance* load = nullptr;
+#ifdef JS_CODEGEN_X86
+ AliasSet aliases = !moduleEnv_.memory->canMovingGrow()
+ ? AliasSet::None()
+ : AliasSet::Load(AliasSet::WasmHeapMeta);
+ load = MWasmLoadInstance::New(alloc(), instancePointer_,
+ wasm::Instance::offsetOfMemoryBase(),
+ MIRType::Pointer, aliases);
+ curBlock_->add(load);
+#endif
+ return load;
+ }
+
+ public:
+ // A value holding the memory base, whether that's HeapReg or some other
+ // register.
+ MWasmHeapBase* memoryBase() {
+ MWasmHeapBase* base = nullptr;
+ AliasSet aliases = !moduleEnv_.memory->canMovingGrow()
+ ? AliasSet::None()
+ : AliasSet::Load(AliasSet::WasmHeapMeta);
+ base = MWasmHeapBase::New(alloc(), instancePointer_, aliases);
+ curBlock_->add(base);
+ return base;
+ }
+
+ private:
+ // If the bounds checking strategy requires it, load the bounds check limit
+ // from the instance.
+ MWasmLoadInstance* maybeLoadBoundsCheckLimit(MIRType type) {
+ MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64);
+ if (moduleEnv_.hugeMemoryEnabled()) {
+ return nullptr;
+ }
+ AliasSet aliases = !moduleEnv_.memory->canMovingGrow()
+ ? AliasSet::None()
+ : AliasSet::Load(AliasSet::WasmHeapMeta);
+ auto* load = MWasmLoadInstance::New(
+ alloc(), instancePointer_, wasm::Instance::offsetOfBoundsCheckLimit(),
+ 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 (moduleEnv_.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()) {
+ 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) {
+ uint32_t offsetGuardLimit =
+ GetMaxOffsetGuardLimit(moduleEnv_.hugeMemoryEnabled());
+
+ if ((*base)->isConstant()) {
+ uint64_t basePtr = 0;
+ if (isMem64()) {
+ 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 += uint32_t(basePtr);
+ access->setOffset32(uint32_t(offset));
+ *base = isMem64() ? 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) {
+ uint32_t offsetGuardLimit =
+ GetMaxOffsetGuardLimit(moduleEnv_.hugeMemoryEnabled());
+
+ if (access->offset64() >= offsetGuardLimit ||
+ access->offset64() > UINT32_MAX || mustAddOffset ||
+ !JitOptions.wasmFoldOffsets) {
+ *base = computeEffectiveAddress(*base, access);
+ }
+ }
+
+ MWasmLoadInstance* needBoundsCheck() {
+#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() &&
+ !moduleEnv_.memory->boundsCheckLimitIs32Bits() &&
+ MaxMemoryPages(moduleEnv_.memory->indexType()) >=
+ 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(
+ mem32LimitIs64Bits || isMem64() ? MIRType::Int64 : MIRType::Int32);
+ }
+
+ void performBoundsCheck(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() && boundsCheckLimit->type() == MIRType::Int64;
+ if (extendAndWrapIndex) {
+ auto* extended = MWasmExtendU32Index::New(alloc(), actualBase);
+ curBlock_->add(extended);
+ actualBase = extended;
+ }
+
+ auto* ins =
+ MWasmBoundsCheck::New(alloc(), actualBase, boundsCheckLimit,
+ bytecodeOffset(), MWasmBoundsCheck::Memory0);
+ 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(!moduleEnv_.isAsmJS());
+
+ // Attempt to fold an offset into a constant base pointer 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(), bytecodeOffset()));
+ }
+
+ // Emit the bounds check if necessary; it traps if it fails. This may
+ // update *base.
+ MWasmLoadInstance* boundsCheckLimit = needBoundsCheck();
+ if (boundsCheckLimit) {
+ performBoundsCheck(base, boundsCheckLimit);
+ }
+
+#ifndef JS_64BIT
+ if (isMem64()) {
+ // 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(!moduleEnv_.hugeMemoryEnabled());
+ 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() { return moduleEnv_.memory->indexType() == IndexType::I32; }
+ bool isMem64() { return moduleEnv_.memory->indexType() == IndexType::I64; }
+
+ // Sometimes, we need to determine the memory type before the opcode reader
+ // that will reject a memory opcode in the presence of no-memory gets a chance
+ // to do so. This predicate is safe.
+ bool isNoMemOrMem32() {
+ return !moduleEnv_.usesMemory() ||
+ moduleEnv_.memory->indexType() == IndexType::I32;
+ }
+
+ // Add the offset into the pointer to yield the EA; trap on overflow.
+ 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, bytecodeOffset());
+ curBlock_->add(ins);
+ access->clearOffset();
+ return ins;
+ }
+
+ MDefinition* load(MDefinition* base, MemoryAccessDesc* access,
+ ValType result) {
+ if (inDeadCode()) {
+ return nullptr;
+ }
+
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ MInstruction* load = nullptr;
+ if (moduleEnv_.isAsmJS()) {
+ MOZ_ASSERT(access->offset64() == 0);
+ MWasmLoadInstance* boundsCheckLimit =
+ maybeLoadBoundsCheckLimit(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;
+ }
+
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ MInstruction* store = nullptr;
+ if (moduleEnv_.isAsmJS()) {
+ MOZ_ASSERT(access->offset64() == 0);
+ MWasmLoadInstance* boundsCheckLimit =
+ maybeLoadBoundsCheckLimit(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;
+ }
+
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ 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;
+ }
+
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ 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;
+ }
+
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ 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(viewType, addr.align, addr.offset,
+ bytecodeIfNotAsmJS());
+
+ // 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(Scalar::Float64, addr.align, addr.offset,
+ bytecodeIfNotAsmJS());
+ 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(viewType, addr.align, addr.offset,
+ bytecodeIfNotAsmJS());
+ 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(Scalar::Simd128, addr.align, addr.offset,
+ bytecodeIfNotAsmJS());
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ MDefinition* base = addr.base;
+ MOZ_ASSERT(!moduleEnv_.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(Scalar::Simd128, addr.align, addr.offset,
+ bytecodeIfNotAsmJS());
+ MWasmLoadInstance* memoryBase = maybeLoadMemoryBase();
+ MDefinition* base = addr.base;
+ MOZ_ASSERT(!moduleEnv_.isAsmJS());
+ 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(unsigned instanceDataOffset, bool isConst,
+ bool isIndirect, MIRType type) {
+ if (inDeadCode()) {
+ return nullptr;
+ }
+
+ MInstruction* load;
+ if (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, instanceDataOffset,
+ /*isConst=*/true, instancePointer_);
+ curBlock_->add(cellPtr);
+ load = MWasmLoadGlobalCell::New(alloc(), type, cellPtr);
+ } else {
+ // Pull the value directly out of Instance::globalArea.
+ load = MWasmLoadInstanceDataField::New(alloc(), type, instanceDataOffset,
+ isConst, instancePointer_);
+ }
+ curBlock_->add(load);
+ return load;
+ }
+
+ [[nodiscard]] bool storeGlobalVar(uint32_t lineOrBytecode,
+ uint32_t instanceDataOffset,
+ bool isIndirect, MDefinition* v) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ if (isIndirect) {
+ // Pull a pointer to the value out of Instance::globalArea, then
+ // store through that pointer.
+ auto* valueAddr = MWasmLoadInstanceDataField::New(
+ alloc(), MIRType::Pointer, instanceDataOffset,
+ /*isConst=*/true, instancePointer_);
+ curBlock_->add(valueAddr);
+
+ // Handle a store to a ref-typed field specially
+ if (v->type() == MIRType::RefOrNull) {
+ // Load the previous value for the post-write barrier
+ auto* prevValue =
+ MWasmLoadGlobalCell::New(alloc(), MIRType::RefOrNull, valueAddr);
+ 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 postBarrierPrecise(lineOrBytecode, 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 (v->type() == MIRType::RefOrNull) {
+ // Compute the address of the ref-typed global
+ auto* valueAddr = MWasmDerivedPointer::New(
+ alloc(), instancePointer_,
+ wasm::Instance::offsetInData(instanceDataOffset));
+ curBlock_->add(valueAddr);
+
+ // Load the previous value for the post-write barrier
+ auto* prevValue =
+ MWasmLoadGlobalCell::New(alloc(), MIRType::RefOrNull, valueAddr);
+ 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 postBarrierPrecise(lineOrBytecode, valueAddr, prevValue);
+ }
+
+ auto* store = MWasmStoreInstanceDataField::New(alloc(), instanceDataOffset,
+ v, instancePointer_);
+ curBlock_->add(store);
+ return true;
+ }
+
+ MDefinition* loadTableField(uint32_t tableIndex, unsigned fieldOffset,
+ MIRType type) {
+ uint32_t instanceDataOffset = wasm::Instance::offsetInData(
+ moduleEnv_.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) {
+ return loadTableField(tableIndex, offsetof(TableInstanceData, length),
+ MIRType::Int32);
+ }
+
+ MDefinition* loadTableElements(uint32_t tableIndex) {
+ return loadTableField(tableIndex, offsetof(TableInstanceData, elements),
+ MIRType::Pointer);
+ }
+
+ MDefinition* tableGetAnyRef(uint32_t tableIndex, MDefinition* index) {
+ // Load the table length and perform a bounds check with spectre index
+ // masking
+ auto* length = loadTableLength(tableIndex);
+ auto* check = MWasmBoundsCheck::New(
+ alloc(), index, length, bytecodeOffset(), MWasmBoundsCheck::Unknown);
+ curBlock_->add(check);
+ if (JitOptions.spectreIndexMasking) {
+ index = check;
+ }
+
+ // Load the table elements and load the element
+ auto* elements = loadTableElements(tableIndex);
+ auto* element = MWasmLoadTableElement::New(alloc(), elements, index);
+ curBlock_->add(element);
+ return element;
+ }
+
+ [[nodiscard]] bool tableSetAnyRef(uint32_t tableIndex, MDefinition* index,
+ MDefinition* value,
+ uint32_t lineOrBytecode) {
+ // Load the table length and perform a bounds check with spectre index
+ // masking
+ auto* length = loadTableLength(tableIndex);
+ auto* check = MWasmBoundsCheck::New(
+ alloc(), index, length, bytecodeOffset(), MWasmBoundsCheck::Unknown);
+ curBlock_->add(check);
+ if (JitOptions.spectreIndexMasking) {
+ index = check;
+ }
+
+ // Load the table elements
+ auto* elements = loadTableElements(tableIndex);
+
+ // Load the previous value
+ auto* prevValue = MWasmLoadTableElement::New(alloc(), elements, index);
+ curBlock_->add(prevValue);
+
+ // Compute the value's location for the post barrier
+ auto* loc =
+ MWasmDerivedIndexPointer::New(alloc(), elements, index, 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 postBarrierPrecise(lineOrBytecode, loc, prevValue);
+ }
+
+ void addInterruptCheck() {
+ if (inDeadCode()) {
+ return;
+ }
+ curBlock_->add(
+ MWasmInterruptCheck::New(alloc(), instancePointer_, bytecodeOffset()));
+ }
+
+ // Perform a post-write barrier to update the generational store buffer. This
+ // version will remove a previous store buffer entry if it is no longer
+ // needed.
+ [[nodiscard]] bool postBarrierPrecise(uint32_t lineOrBytecode,
+ MDefinition* valueAddr,
+ MDefinition* value) {
+ return emitInstanceCall2(lineOrBytecode, SASigPostBarrierPrecise, valueAddr,
+ value);
+ }
+
+ // Perform a post-write barrier to update the generational store buffer. This
+ // version will remove a previous store buffer entry if it is no longer
+ // needed.
+ [[nodiscard]] bool postBarrierPreciseWithOffset(uint32_t lineOrBytecode,
+ MDefinition* valueBase,
+ uint32_t valueOffset,
+ MDefinition* value) {
+ MDefinition* valueOffsetDef = constantI32(int32_t(valueOffset));
+ if (!valueOffsetDef) {
+ return false;
+ }
+ return emitInstanceCall3(lineOrBytecode, SASigPostBarrierPreciseWithOffset,
+ valueBase, valueOffsetDef, value);
+ }
+
+ // Perform a post-write barrier to update the generational store buffer. This
+ // version is the most efficient and only requires the address to store the
+ // value and the new value. It does not remove a previous store buffer entry
+ // if it is no longer needed, you must use a precise post-write barrier for
+ // that.
+ [[nodiscard]] bool postBarrier(uint32_t lineOrBytecode, MDefinition* object,
+ MDefinition* valueBase, uint32_t valueOffset,
+ MDefinition* newValue) {
+ auto* barrier = MWasmPostWriteBarrier::New(
+ alloc(), instancePointer_, object, valueBase, valueOffset, 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).
+
+ // Operations that modify a CallCompileState.
+
+ [[nodiscard]] bool passInstance(MIRType instanceType,
+ CallCompileState* args) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ // Should only pass an instance once. And it must be a non-GC pointer.
+ MOZ_ASSERT(args->instanceArg_ == ABIArg());
+ MOZ_ASSERT(instanceType == MIRType::Pointer);
+ args->instanceArg_ = args->abi_.next(MIRType::Pointer);
+ return true;
+ }
+
+ // Do not call this directly. Call one of the passArg() variants instead.
+ [[nodiscard]] bool passArgWorker(MDefinition* argDef, MIRType type,
+ CallCompileState* call) {
+ ABIArg arg = call->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 call->regArgs_.append(
+ MWasmCallBase::Arg(AnyRegister(arg.gpr64().low), mirLow)) &&
+ call->regArgs_.append(
+ MWasmCallBase::Arg(AnyRegister(arg.gpr64().high), mirHigh));
+ }
+#endif
+ case ABIArg::GPR:
+ case ABIArg::FPU:
+ return call->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 SpanT>
+ [[nodiscard]] bool passArgs(const DefVector& argDefs, SpanT types,
+ CallCompileState* call) {
+ MOZ_ASSERT(argDefs.length() == types.size());
+ for (uint32_t i = 0; i < argDefs.length(); i++) {
+ MDefinition* def = argDefs[i];
+ ValType type = types[i];
+ if (!passArg(def, type, call)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool passArg(MDefinition* argDef, MIRType type,
+ CallCompileState* call) {
+ if (inDeadCode()) {
+ return true;
+ }
+ return passArgWorker(argDef, type, call);
+ }
+
+ [[nodiscard]] bool passArg(MDefinition* argDef, ValType type,
+ CallCompileState* call) {
+ if (inDeadCode()) {
+ return true;
+ }
+ return passArgWorker(argDef, type.toMIRType(), call);
+ }
+
+ // 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* call) {
+ if (inDeadCode()) {
+ return true;
+ }
+ ABIResultIter iter(resultType);
+ while (!iter.done() && iter.cur().inRegister()) {
+ iter.next();
+ }
+ if (iter.done()) {
+ // No stack results.
+ return true;
+ }
+
+ 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);
+ if (!passArg(stackResultArea, MIRType::Pointer, call)) {
+ return false;
+ }
+ call->stackResultArea_ = stackResultArea;
+ return true;
+ }
+
+ [[nodiscard]] bool finishCall(CallCompileState* call) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ if (!call->regArgs_.append(
+ MWasmCallBase::Arg(AnyRegister(InstanceReg), instancePointer_))) {
+ return false;
+ }
+
+ uint32_t stackBytes = call->abi_.stackBytesConsumedSoFar();
+
+ maxStackArgBytes_ = std::max(maxStackArgBytes_, stackBytes);
+ return true;
+ }
+
+ // Wrappers for creating various kinds of calls.
+
+ [[nodiscard]] bool collectUnaryCallResult(MIRType type,
+ MDefinition** result) {
+ 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:
+ def = MWasmFloatRegisterResult::New(alloc(), type, ReturnFloat32Reg);
+ break;
+ case MIRType::Double:
+ def = MWasmFloatRegisterResult::New(alloc(), type, ReturnDoubleReg);
+ break;
+#ifdef ENABLE_WASM_SIMD
+ case MIRType::Simd128:
+ def = MWasmFloatRegisterResult::New(alloc(), type, ReturnSimd128Reg);
+ break;
+#endif
+ case MIRType::RefOrNull:
+ def = MWasmRegisterResult::New(alloc(), MIRType::RefOrNull, ReturnReg);
+ break;
+ default:
+ MOZ_CRASH("unexpected MIRType result for builtin call");
+ }
+
+ if (!def) {
+ return false;
+ }
+
+ curBlock_->add(def);
+ *result = def;
+
+ return true;
+ }
+
+ [[nodiscard]] bool collectCallResults(const ResultType& type,
+ MWasmStackResultArea* stackResultArea,
+ DefVector* results) {
+ 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::RefOrNull,
+ result.gpr());
+ 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(stackResultArea);
+ MOZ_ASSERT(stackResultCount);
+ uint32_t idx = --stackResultCount;
+ def = MWasmStackResult::New(alloc(), stackResultArea, idx);
+ }
+
+ if (!def) {
+ return false;
+ }
+ curBlock_->add(def);
+ results->infallibleAppend(def);
+ }
+
+ MOZ_ASSERT(results->length() == type.length());
+
+ return true;
+ }
+
+ [[nodiscard]] bool catchableCall(const CallSiteDesc& desc,
+ const CalleeDesc& callee,
+ const MWasmCallBase::Args& args,
+ const ArgTypeVector& argTypes,
+ MDefinition* indexOrRef = nullptr) {
+ MWasmCallTryDesc tryDesc;
+ if (!beginTryCall(&tryDesc)) {
+ return false;
+ }
+
+ MInstruction* ins;
+ if (tryDesc.inTry) {
+ ins = MWasmCallCatchable::New(alloc(), desc, callee, args,
+ StackArgAreaSizeUnaligned(argTypes),
+ tryDesc, indexOrRef);
+ } else {
+ ins = MWasmCallUncatchable::New(alloc(), desc, callee, args,
+ StackArgAreaSizeUnaligned(argTypes),
+ indexOrRef);
+ }
+ if (!ins) {
+ return false;
+ }
+ curBlock_->add(ins);
+
+ return finishTryCall(&tryDesc);
+ }
+
+ [[nodiscard]] bool callDirect(const FuncType& funcType, uint32_t funcIndex,
+ uint32_t lineOrBytecode,
+ const CallCompileState& call,
+ DefVector* results) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Func);
+ ResultType resultType = ResultType::Vector(funcType.results());
+ auto callee = CalleeDesc::function(funcIndex);
+ ArgTypeVector args(funcType);
+
+ if (!catchableCall(desc, callee, call.regArgs_, args)) {
+ return false;
+ }
+ return collectCallResults(resultType, call.stackResultArea_, results);
+ }
+
+ [[nodiscard]] bool callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex,
+ MDefinition* index, uint32_t lineOrBytecode,
+ const CallCompileState& call,
+ DefVector* results) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ const FuncType& funcType = (*moduleEnv_.types)[funcTypeIndex].funcType();
+ CallIndirectId callIndirectId =
+ CallIndirectId::forFuncType(moduleEnv_, funcTypeIndex);
+
+ CalleeDesc callee;
+ if (moduleEnv_.isAsmJS()) {
+ MOZ_ASSERT(tableIndex == 0);
+ MOZ_ASSERT(callIndirectId.kind() == CallIndirectIdKind::AsmJS);
+ uint32_t tableIndex = moduleEnv_.asmJSSigToTableIndex[funcTypeIndex];
+ const TableDesc& table = moduleEnv_.tables[tableIndex];
+ MOZ_ASSERT(IsPowerOfTwo(table.initialLength));
+
+ MDefinition* mask = constantI32(int32_t(table.initialLength - 1));
+ MBitAnd* maskedIndex = MBitAnd::New(alloc(), index, mask, MIRType::Int32);
+ curBlock_->add(maskedIndex);
+
+ index = maskedIndex;
+ callee = CalleeDesc::asmJSTable(moduleEnv_, tableIndex);
+ } else {
+ MOZ_ASSERT(callIndirectId.kind() != CallIndirectIdKind::AsmJS);
+ const TableDesc& table = moduleEnv_.tables[tableIndex];
+ callee =
+ CalleeDesc::wasmTable(moduleEnv_, table, tableIndex, callIndirectId);
+ }
+
+ CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Indirect);
+ ArgTypeVector args(funcType);
+ ResultType resultType = ResultType::Vector(funcType.results());
+
+ if (!catchableCall(desc, callee, call.regArgs_, args, index)) {
+ return false;
+ }
+ return collectCallResults(resultType, call.stackResultArea_, results);
+ }
+
+ [[nodiscard]] bool callImport(unsigned instanceDataOffset,
+ uint32_t lineOrBytecode,
+ const CallCompileState& call,
+ const FuncType& funcType, DefVector* results) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Import);
+ auto callee = CalleeDesc::import(instanceDataOffset);
+ ArgTypeVector args(funcType);
+ ResultType resultType = ResultType::Vector(funcType.results());
+
+ if (!catchableCall(desc, callee, call.regArgs_, args)) {
+ return false;
+ }
+ return collectCallResults(resultType, call.stackResultArea_, results);
+ }
+
+ [[nodiscard]] bool builtinCall(const SymbolicAddressSignature& builtin,
+ uint32_t lineOrBytecode,
+ const CallCompileState& call,
+ MDefinition** def) {
+ if (inDeadCode()) {
+ *def = nullptr;
+ return true;
+ }
+
+ MOZ_ASSERT(builtin.failureMode == FailureMode::Infallible);
+
+ CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
+ auto callee = CalleeDesc::builtin(builtin.identity);
+ auto* ins = MWasmCallUncatchable::New(alloc(), desc, callee, call.regArgs_,
+ StackArgAreaSizeUnaligned(builtin));
+ if (!ins) {
+ return false;
+ }
+
+ curBlock_->add(ins);
+
+ return collectUnaryCallResult(builtin.retType, def);
+ }
+
+ [[nodiscard]] bool builtinInstanceMethodCall(
+ const SymbolicAddressSignature& builtin, uint32_t lineOrBytecode,
+ const CallCompileState& call, MDefinition** def = nullptr) {
+ MOZ_ASSERT_IF(!def, builtin.retType == MIRType::None);
+ if (inDeadCode()) {
+ if (def) {
+ *def = nullptr;
+ }
+ return true;
+ }
+
+ CallSiteDesc desc(lineOrBytecode, CallSiteDesc::Symbolic);
+ auto* ins = MWasmCallUncatchable::NewBuiltinInstanceMethodCall(
+ alloc(), desc, builtin.identity, builtin.failureMode, call.instanceArg_,
+ call.regArgs_, StackArgAreaSizeUnaligned(builtin));
+ if (!ins) {
+ return false;
+ }
+
+ curBlock_->add(ins);
+
+ return def ? collectUnaryCallResult(builtin.retType, def) : true;
+ }
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ [[nodiscard]] bool callRef(const FuncType& funcType, MDefinition* ref,
+ uint32_t lineOrBytecode,
+ const CallCompileState& call, DefVector* results) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ CalleeDesc callee = CalleeDesc::wasmFuncRef();
+
+ CallSiteDesc desc(lineOrBytecode, CallSiteDesc::FuncRef);
+ ArgTypeVector args(funcType);
+ ResultType resultType = ResultType::Vector(funcType.results());
+
+ if (!catchableCall(desc, callee, call.regArgs_, args, ref)) {
+ return false;
+ }
+ return collectCallResults(resultType, call.stackResultArea_, results);
+ }
+
+#endif // ENABLE_WASM_FUNCTION_REFERENCES
+
+ /*********************************************** Control flow generation */
+
+ inline bool inDeadCode() const { return curBlock_ == nullptr; }
+
+ [[nodiscard]] bool returnValues(const DefVector& values) {
+ if (inDeadCode()) {
+ return true;
+ }
+
+ 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);
+ if (result.type().isRefRepr()) {
+ auto* store = MWasmStoreRef::New(
+ alloc(), instancePointer_, stackResultPointer_,
+ result.stackOffset(), values[i], AliasSet::WasmStackResult,
+ WasmPreBarrierKind::None);
+ curBlock_->add(store);
+ } else {
+ 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, bytecodeOffset());
+ 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(blockDepth_ < blockPatches_.length(),
+ blockPatches_[blockDepth_].empty());
+ blockDepth_++;
+ return true;
+ }
+
+ [[nodiscard]] bool finishBlock(DefVector* defs) {
+ MOZ_ASSERT(blockDepth_);
+ uint32_t topLabel = --blockDepth_;
+ return bindBranches(topLabel, defs);
+ }
+
+ [[nodiscard]] bool startLoop(MBasicBlock** loopHeader, size_t paramCount) {
+ *loopHeader = nullptr;
+
+ blockDepth_++;
+ loopDepth_++;
+
+ if (inDeadCode()) {
+ return true;
+ }
+
+ // Create the loop header.
+ MOZ_ASSERT(curBlock_->loopDepth() == loopDepth_ - 1);
+ *loopHeader = MBasicBlock::New(mirGraph(), info(), curBlock_,
+ MBasicBlock::PENDING_LOOP_HEADER);
+ if (!*loopHeader) {
+ return false;
+ }
+
+ (*loopHeader)->setLoopDepth(loopDepth_);
+ mirGraph().addBlock(*loopHeader);
+ curBlock_->end(MGoto::New(alloc(), *loopHeader));
+
+ DefVector loopParams;
+ if (!iter().getResults(paramCount, &loopParams)) {
+ return false;
+ }
+ 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;
+ }
+
+ // Flag all redundant phis as unused.
+ 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 (ControlFlowPatchVector& patches : blockPatches_) {
+ for (ControlFlowPatch& p : 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::Body) {
+ continue;
+ }
+ Control& control = iter().controlItem(depth);
+ for (MControlInstruction* patch : control.tryPadPatches) {
+ MBasicBlock* block = patch->block();
+ if (block->loopDepth() >= loopEntry->loopDepth()) {
+ fixupRedundantPhis(block);
+ }
+ }
+ }
+
+ // 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(blockDepth_ >= 1);
+ MOZ_ASSERT(loopDepth_);
+
+ uint32_t headerLabel = blockDepth_ - 1;
+
+ if (!loopHeader) {
+ MOZ_ASSERT(inDeadCode());
+ MOZ_ASSERT(headerLabel >= blockPatches_.length() ||
+ blockPatches_[headerLabel].empty());
+ blockDepth_--;
+ loopDepth_--;
+ 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() == 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() == loopDepth_);
+ curBlock_->end(MGoto::New(alloc(), loopHeader));
+ if (!setLoopBackedge(loopHeader, loopBody, curBlock_,
+ backedgeValues.length())) {
+ return false;
+ }
+ }
+
+ curBlock_ = loopBody;
+
+ loopDepth_--;
+
+ // If the loop depth still at the inner loop body, correct it.
+ if (curBlock_ && curBlock_->loopDepth() != loopDepth_) {
+ MBasicBlock* out;
+ if (!goToNewBlock(curBlock_, &out)) {
+ return false;
+ }
+ curBlock_ = out;
+ }
+
+ blockDepth_ -= 1;
+ return inDeadCode() || popPushedDefs(loopResults);
+ }
+
+ [[nodiscard]] bool addControlFlowPatch(MControlInstruction* ins,
+ uint32_t relative, uint32_t index) {
+ MOZ_ASSERT(relative < blockDepth_);
+ uint32_t absolute = blockDepth_ - 1 - relative;
+
+ if (absolute >= blockPatches_.length() &&
+ !blockPatches_.resize(absolute + 1)) {
+ return false;
+ }
+
+ return blockPatches_[absolute].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) {
+ 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)) {
+ 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 inTryBlock(uint32_t* relativeDepth) {
+ return iter().controlFindInnermost(LabelKind::Try, relativeDepth);
+ }
+
+ bool inTryCode() {
+ uint32_t relativeDepth;
+ return inTryBlock(&relativeDepth);
+ }
+
+ MDefinition* loadTag(uint32_t tagIndex) {
+ MWasmLoadInstanceDataField* tag = MWasmLoadInstanceDataField::New(
+ alloc(), MIRType::RefOrNull,
+ moduleEnv_.offsetOfTagInstanceData(tagIndex), true, instancePointer_);
+ curBlock_->add(tag);
+ return tag;
+ }
+
+ void loadPendingExceptionState(MInstruction** exception, MInstruction** tag) {
+ *exception = MWasmLoadInstance::New(
+ alloc(), instancePointer_, wasm::Instance::offsetOfPendingException(),
+ MIRType::RefOrNull, AliasSet::Load(AliasSet::WasmPendingException));
+ curBlock_->add(*exception);
+
+ *tag = MWasmLoadInstance::New(
+ alloc(), instancePointer_,
+ wasm::Instance::offsetOfPendingExceptionTag(), MIRType::RefOrNull,
+ AliasSet::Load(AliasSet::WasmPendingException));
+ curBlock_->add(*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 (!postBarrierPrecise(/*lineOrBytecode=*/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 postBarrierPrecise(/*lineOrBytecode=*/0, exceptionTagAddr, tag);
+ }
+
+ [[nodiscard]] bool addPadPatch(MControlInstruction* ins,
+ size_t relativeTryDepth) {
+ Control& tryControl = iter().controlItem(relativeTryDepth);
+ ControlInstructionVector& padPatches = tryControl.tryPadPatches;
+ return padPatches.emplaceBack(ins);
+ }
+
+ [[nodiscard]] bool endWithPadPatch(uint32_t relativeTryDepth) {
+ MGoto* jumpToLandingPad = MGoto::New(alloc());
+ curBlock_->end(jumpToLandingPad);
+ return addPadPatch(jumpToLandingPad, relativeTryDepth);
+ }
+
+ [[nodiscard]] bool delegatePadPatches(const ControlInstructionVector& patches,
+ uint32_t relativeDepth) {
+ if (patches.empty()) {
+ return true;
+ }
+
+ // Find where we are delegating the pad patches to.
+ uint32_t targetRelativeDepth;
+ if (!iter().controlFindInnermostFrom(LabelKind::Try, relativeDepth,
+ &targetRelativeDepth)) {
+ MOZ_ASSERT(relativeDepth <= blockDepth_ - 1);
+ targetRelativeDepth = blockDepth_ - 1;
+ }
+ // Append the delegate's pad patches to the target's.
+ for (MControlInstruction* ins : patches) {
+ if (!addPadPatch(ins, targetRelativeDepth)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ [[nodiscard]] bool beginTryCall(MWasmCallTryDesc* call) {
+ call->inTry = inTryBlock(&call->relativeTryDepth);
+ if (!call->inTry) {
+ return true;
+ }
+ // Allocate a try note
+ if (!tryNotes_.append(wasm::TryNote())) {
+ return false;
+ }
+ call->tryNoteIndex = tryNotes_.length() - 1;
+ // Allocate blocks for fallthrough and exceptions
+ return newBlock(curBlock_, &call->fallthroughBlock) &&
+ newBlock(curBlock_, &call->prePadBlock);
+ }
+
+ [[nodiscard]] bool finishTryCall(MWasmCallTryDesc* call) {
+ if (!call->inTry) {
+ return true;
+ }
+
+ // Switch to the prePadBlock
+ MBasicBlock* callBlock = curBlock_;
+ curBlock_ = call->prePadBlock;
+
+ // Mark this as the landing pad for the call
+ curBlock_->add(
+ MWasmCallLandingPrePad::New(alloc(), callBlock, call->tryNoteIndex));
+
+ // End with a pending jump to the landing pad
+ if (!endWithPadPatch(call->relativeTryDepth)) {
+ return false;
+ }
+
+ // Compilation continues in the fallthroughBlock.
+ curBlock_ = call->fallthroughBlock;
+ return true;
+ }
+
+ // Create a landing pad for a try block if there are any throwing
+ // instructions.
+ [[nodiscard]] bool createTryLandingPadIfNeeded(Control& control,
+ MBasicBlock** landingPad) {
+ // If there are no pad-patches for this try control, it means there are no
+ // instructions in the try code that could throw an exception. In this
+ // case, all the catches are dead code, and the try code ends up equivalent
+ // to a plain wasm block.
+ ControlInstructionVector& patches = control.tryPadPatches;
+ if (patches.empty()) {
+ *landingPad = nullptr;
+ return true;
+ }
+
+ // Otherwise, if there are (pad-) branches from places in the try code that
+ // may throw an exception, bind these branches to a new landing pad
+ // block. This is done similarly to what is done in bindBranches.
+ MControlInstruction* ins = patches[0];
+ MBasicBlock* pred = ins->block();
+ if (!newBlock(pred, landingPad)) {
+ return false;
+ }
+ ins->replaceSuccessor(0, *landingPad);
+ for (size_t i = 1; i < patches.length(); i++) {
+ ins = patches[i];
+ pred = ins->block();
+ if (!(*landingPad)->addPredecessor(alloc(), pred)) {
+ return false;
+ }
+ ins->replaceSuccessor(0, *landingPad);
+ }
+
+ // Set up the slots in the landing pad block.
+ if (!setupLandingPadSlots(*landingPad)) {
+ return false;
+ }
+
+ // Clear the now bound pad patches.
+ patches.clear();
+ return true;
+ }
+
+ // Consume the pending exception state from instance, and set up the slots
+ // of the landing pad with the exception state.
+ [[nodiscard]] bool setupLandingPadSlots(MBasicBlock* landingPad) {
+ MBasicBlock* prevBlock = curBlock_;
+ curBlock_ = landingPad;
+
+ // Load the pending exception and tag
+ MInstruction* exception;
+ MInstruction* tag;
+ loadPendingExceptionState(&exception, &tag);
+
+ // Clear the pending exception and tag
+ auto* null = constantNullRef();
+ if (!setPendingExceptionState(null, null)) {
+ return false;
+ }
+
+ // Push the exception and its tag on the stack to make them available
+ // to the landing pad blocks.
+ if (!landingPad->ensureHasSlots(2)) {
+ return false;
+ }
+ landingPad->push(exception);
+ landingPad->push(tag);
+
+ curBlock_ = prevBlock;
+ return true;
+ }
+
+ [[nodiscard]] bool startTry(MBasicBlock** curBlock) {
+ *curBlock = curBlock_;
+ 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, const LabelKind& fromKind,
+ uint32_t tagIndex) {
+ // 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) {
+ MBasicBlock* padBlock = nullptr;
+ if (!createTryLandingPadIfNeeded(control, &padBlock)) {
+ return false;
+ }
+ // Set the control block for this try-catch to the landing pad.
+ control.block = padBlock;
+ }
+
+ // 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;
+
+ // 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;
+ // Remove the tag and exception slots from the block, they are no
+ // longer necessary.
+ curBlock_->pop();
+ curBlock_->pop();
+ 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;
+ }
+
+ // Get the exception and its tag from the slots we pushed when adding
+ // control flow patches.
+ MDefinition* exceptionTag = curBlock_->pop();
+ MDefinition* exception = curBlock_->pop();
+
+ // Branch to the catch block if the exception's tag matches this catch
+ // block's tag.
+ MDefinition* catchTag = loadTag(tagIndex);
+ MDefinition* matchesCatchTag =
+ compare(exceptionTag, catchTag, JSOp::Eq, MCompare::Compare_RefOrNull);
+ 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;
+
+ // Remove the tag and exception slots from the block, they are no
+ // longer necessary.
+ curBlock_->pop();
+ curBlock_->pop();
+
+ // Extract the exception values for the catch block
+ DefVector values;
+ if (!loadExceptionValues(exception, tagIndex, &values)) {
+ return false;
+ }
+ iter().setResults(values.length(), values);
+ return true;
+ }
+
+ [[nodiscard]] bool loadExceptionValues(MDefinition* exception,
+ uint32_t tagIndex, DefVector* values) {
+ SharedTagType tagType = moduleEnv().tags[tagIndex].type;
+ const ValTypeVector& params = tagType->argTypes_;
+ const TagOffsetVector& offsets = tagType->argOffsets_;
+
+ // Get the data pointer from the exception object
+ auto* data = MWasmLoadField::New(
+ alloc(), exception, WasmExceptionObject::offsetOfData(),
+ 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 = MWasmLoadFieldKA::New(
+ alloc(), exception, data, offsets[i], params[i].toMIRType(),
+ MWideningOp::None, AliasSet::Load(AliasSet::Any));
+ 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 emitBodyDelegateThrowPad. We
+ // specify a relativeDepth of '1' to delegate outside of the still
+ // active try block.
+ uint32_t relativeDepth = 1;
+ if (!delegatePadPatches(control.tryPadPatches, relativeDepth)) {
+ return false;
+ }
+ break;
+ }
+ case LabelKind::Catch: {
+ // 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;
+ MDefinition* tag = curBlock_->pop();
+ MDefinition* exception = curBlock_->pop();
+ if (!throwFrom(exception, tag)) {
+ return false;
+ }
+ curBlock_ = prevBlock;
+ }
+ break;
+ }
+ case LabelKind::CatchAll:
+ // 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 emitBodyDelegateThrowPad(Control& control) {
+ // Create a landing pad for any throwing instructions
+ MBasicBlock* padBlock;
+ if (!createTryLandingPadIfNeeded(control, &padBlock)) {
+ return false;
+ }
+
+ // If no landing pad was necessary, then we don't need to do anything here
+ if (!padBlock) {
+ return true;
+ }
+
+ // Switch to the landing pad and rethrow the exception
+ MBasicBlock* prevBlock = curBlock_;
+ curBlock_ = padBlock;
+ MDefinition* tag = curBlock_->pop();
+ MDefinition* exception = curBlock_->pop();
+ if (!throwFrom(exception, tag)) {
+ return false;
+ }
+ curBlock_ = prevBlock;
+ 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, WasmExceptionObject::offsetOfData(),
+ MIRType::Pointer, MWideningOp::None, AliasSet::Load(AliasSet::Any));
+ if (!data) {
+ return false;
+ }
+ curBlock_->add(data);
+
+ // Store the params into the data pointer
+ SharedTagType tagType = moduleEnv_.tags[tagIndex].type;
+ for (size_t i = 0; i < tagType->argOffsets_.length(); i++) {
+ if (!mirGen_.ensureBallast()) {
+ return false;
+ }
+ ValType type = tagType->argTypes_[i];
+ uint32_t offset = tagType->argOffsets_[i];
+
+ if (!type.isRefRepr()) {
+ auto* store = MWasmStoreFieldKA::New(alloc(), exception, data, offset,
+ argValues[i], MNarrowingOp::None,
+ AliasSet::Store(AliasSet::Any));
+ if (!store) {
+ return false;
+ }
+ curBlock_->add(store);
+ continue;
+ }
+
+ // Store the new value
+ auto* store = MWasmStoreFieldRefKA::New(
+ alloc(), instancePointer_, exception, data, offset, argValues[i],
+ AliasSet::Store(AliasSet::Any), Nothing(), WasmPreBarrierKind::None);
+ if (!store) {
+ return false;
+ }
+ curBlock_->add(store);
+
+ // Call the post-write barrier
+ if (!postBarrier(bytecodeOffset, exception, data, offset, argValues[i])) {
+ return false;
+ }
+ }
+
+ // Throw the exception
+ return throwFrom(exception, tag);
+ }
+
+ [[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.
+ uint32_t relativeTryDepth;
+ if (inTryBlock(&relativeTryDepth)) {
+ // 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(relativeTryDepth)) {
+ 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);
+ MBasicBlock* pad = control.block;
+ MOZ_ASSERT(pad);
+ MOZ_ASSERT(pad->nslots() > 1);
+ MOZ_ASSERT(iter().controlKind(relativeDepth) == LabelKind::Catch ||
+ iter().controlKind(relativeDepth) == LabelKind::CatchAll);
+
+ // The exception will always be the last slot in the landing pad.
+ size_t exnSlotPosition = pad->nslots() - 2;
+ MDefinition* tag = pad->getSlot(exnSlotPosition + 1);
+ MDefinition* exception = pad->getSlot(exnSlotPosition);
+ MOZ_ASSERT(exception->type() == MIRType::RefOrNull &&
+ tag->type() == MIRType::RefOrNull);
+ return throwFrom(exception, tag);
+ }
+
+ /*********************************************** 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 lineOrBytecode,
+ 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. `passInstance`, `passArg`, `finishCall` and
+ // `builtinInstanceMethodCall` 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 ccsArgs;
+ if (!passInstance(callee.argTypes[0], &ccsArgs)) {
+ return false;
+ }
+ for (size_t i = 0; i < numArgs; i++) {
+ if (!passArg(args[i], callee.argTypes[i + 1], &ccsArgs)) {
+ return false;
+ }
+ }
+ if (!finishCall(&ccsArgs)) {
+ return false;
+ }
+ return builtinInstanceMethodCall(callee, lineOrBytecode, ccsArgs, 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);
+ }
+
+ /******************************** WasmGC: low level load/store helpers ***/
+
+ // Given a (FieldType, FieldExtension) pair, produce the (MIRType,
+ // MWideningOp) pair that will give the correct operation for reading the
+ // value from memory.
+ static void fieldLoadInfoToMIR(FieldType type, FieldWideningOp wideningOp,
+ MIRType* mirType, MWideningOp* mirWideningOp) {
+ switch (type.kind()) {
+ case FieldType::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 FieldType::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 FieldType, produce the MNarrowingOp required for writing the
+ // value to memory.
+ static MNarrowingOp fieldStoreInfoToMIR(FieldType type) {
+ switch (type.kind()) {
+ case FieldType::I8:
+ return MNarrowingOp::To8;
+ case FieldType::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 lineOrBytecode, FieldType fieldType, MDefinition* keepAlive,
+ AliasSet::Flag aliasBitset, MDefinition* value, MDefinition* base,
+ uint32_t offset, bool needsTrapInfo, WasmPreBarrierKind preBarrierKind) {
+ MOZ_ASSERT(aliasBitset != 0);
+ MOZ_ASSERT(keepAlive->type() == MIRType::RefOrNull);
+ MOZ_ASSERT(fieldType.widenToValType().toMIRType() == value->type());
+ MNarrowingOp narrowingOp = fieldStoreInfoToMIR(fieldType);
+
+ if (!fieldType.isRefRepr()) {
+ MaybeTrapSiteInfo maybeTrap;
+ if (needsTrapInfo) {
+ maybeTrap.emplace(getTrapSiteInfo());
+ }
+ auto* store = MWasmStoreFieldKA::New(
+ alloc(), keepAlive, base, offset, 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(fieldType.widenToValType() == fieldType.valType());
+
+ // Store the new value
+ auto* store = MWasmStoreFieldRefKA::New(
+ alloc(), instancePointer_, keepAlive, base, offset, value,
+ AliasSet::Store(aliasBitset), mozilla::Some(getTrapSiteInfo()),
+ preBarrierKind);
+ if (!store) {
+ return false;
+ }
+ curBlock_->add(store);
+
+ // Call the post-write barrier
+ return postBarrier(lineOrBytecode, keepAlive, base, offset, value);
+ }
+
+ // 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 lineOrBytecode, FieldType fieldType, MDefinition* keepAlive,
+ AliasSet::Flag aliasBitset, MDefinition* value, MDefinition* base,
+ uint32_t scale, MDefinition* index, WasmPreBarrierKind preBarrierKind) {
+ MOZ_ASSERT(aliasBitset != 0);
+ MOZ_ASSERT(keepAlive->type() == MIRType::RefOrNull);
+ MOZ_ASSERT(fieldType.widenToValType().toMIRType() == value->type());
+ MOZ_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8 ||
+ scale == 16);
+
+ // Currently there's no single MIR node that this can be translated into.
+ // So compute the final address "manually", then store directly to that
+ // address. See bug 1802287.
+ MDefinition* scaleDef = constantTargetWord(intptr_t(scale));
+ if (!scaleDef) {
+ return false;
+ }
+ MDefinition* finalAddr = computeBasePlusScaledIndex(base, scaleDef, index);
+ if (!finalAddr) {
+ return false;
+ }
+
+ return writeGcValueAtBasePlusOffset(
+ lineOrBytecode, fieldType, keepAlive, aliasBitset, value, finalAddr,
+ /*offset=*/0,
+ /*needsTrapInfo=*/false, preBarrierKind);
+ }
+
+ // Generate a read from address `base + offset`, where `offset` is known at
+ // JIT time. The loaded value will be widened as described by `fieldType`
+ // 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(
+ FieldType fieldType, FieldWideningOp fieldWideningOp,
+ MDefinition* keepAlive, AliasSet::Flag aliasBitset, MDefinition* base,
+ uint32_t offset, bool needsTrapInfo) {
+ MOZ_ASSERT(aliasBitset != 0);
+ MOZ_ASSERT(keepAlive->type() == MIRType::RefOrNull);
+ MIRType mirType;
+ MWideningOp mirWideningOp;
+ fieldLoadInfoToMIR(fieldType, fieldWideningOp, &mirType, &mirWideningOp);
+ MaybeTrapSiteInfo maybeTrap;
+ if (needsTrapInfo) {
+ maybeTrap.emplace(getTrapSiteInfo());
+ }
+ auto* load = MWasmLoadFieldKA::New(alloc(), keepAlive, base, offset,
+ mirType, mirWideningOp,
+ AliasSet::Load(aliasBitset), maybeTrap);
+ 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
+ // `fieldType` and `fieldWideningOp`. `keepAlive` will be referenced by the
+ // instruction so as to hold it live (from the GC's point of view).
+ [[nodiscard]] MDefinition* readGcValueAtBasePlusScaledIndex(
+ FieldType fieldType, FieldWideningOp fieldWideningOp,
+ MDefinition* keepAlive, AliasSet::Flag aliasBitset, MDefinition* base,
+ uint32_t scale, MDefinition* index) {
+ MOZ_ASSERT(aliasBitset != 0);
+ MOZ_ASSERT(keepAlive->type() == MIRType::RefOrNull);
+ MOZ_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8 ||
+ scale == 16);
+
+ // Currently there's no single MIR node that this can be translated into.
+ // So compute the final address "manually", then store directly to that
+ // address. See bug 1802287.
+ MDefinition* scaleDef = constantTargetWord(intptr_t(scale));
+ if (!scaleDef) {
+ return nullptr;
+ }
+ MDefinition* finalAddr = computeBasePlusScaledIndex(base, scaleDef, index);
+ if (!finalAddr) {
+ return nullptr;
+ }
+
+ MIRType mirType;
+ MWideningOp mirWideningOp;
+ fieldLoadInfoToMIR(fieldType, fieldWideningOp, &mirType, &mirWideningOp);
+ auto* load = MWasmLoadFieldKA::New(alloc(), keepAlive, finalAddr,
+ /*offset=*/0, mirType, mirWideningOp,
+ AliasSet::Load(aliasBitset),
+ mozilla::Some(getTrapSiteInfo()));
+ 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 superTypeVectorOffset =
+ moduleEnv().offsetOfSuperTypeVector(typeIndex);
+
+ auto* load = MWasmLoadInstanceDataField::New(
+ alloc(), MIRType::Pointer, superTypeVectorOffset,
+ /*isConst=*/true, instancePointer_);
+ if (!load) {
+ return nullptr;
+ }
+ curBlock_->add(load);
+ return load;
+ }
+
+ [[nodiscard]] MDefinition* loadTypeDefInstanceData(uint32_t typeIndex) {
+ size_t offset = Instance::offsetInData(
+ moduleEnv_.offsetOfTypeDefInstanceData(typeIndex));
+ auto* result = MWasmDerivedPointer::New(alloc(), instancePointer_, offset);
+ if (!result) {
+ return nullptr;
+ }
+ curBlock_->add(result);
+ return result;
+ }
+
+ /********************************************** WasmGC: struct helpers ***/
+
+ // 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 lineOrBytecode, const StructField& field,
+ MDefinition* structObject, MDefinition* value,
+ WasmPreBarrierKind preBarrierKind) {
+ FieldType fieldType = field.type;
+ uint32_t fieldOffset = field.offset;
+
+ bool areaIsOutline;
+ uint32_t areaOffset;
+ WasmStructObject::fieldOffsetToAreaAndOffset(fieldType, fieldOffset,
+ &areaIsOutline, &areaOffset);
+
+ // Make `base` point at the first byte of either the struct object as a
+ // whole or of the out-of-line data area. And adjust `areaOffset`
+ // accordingly.
+ MDefinition* base;
+ bool needsTrapInfo;
+ if (areaIsOutline) {
+ auto* load = MWasmLoadField::New(
+ alloc(), structObject, WasmStructObject::offsetOfOutlineData(),
+ MIRType::Pointer, MWideningOp::None,
+ AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
+ mozilla::Some(getTrapSiteInfo()));
+ if (!load) {
+ return false;
+ }
+ curBlock_->add(load);
+ base = load;
+ needsTrapInfo = false;
+ } else {
+ base = structObject;
+ needsTrapInfo = true;
+ areaOffset += WasmStructObject::offsetOfInlineData();
+ }
+ // The transaction is to happen at `base + areaOffset`, so to speak.
+ // After this point we must ignore `fieldOffset`.
+
+ // The alias set denoting the field's location, although lacking a
+ // Load-vs-Store indication at this point.
+ AliasSet::Flag fieldAliasSet = areaIsOutline
+ ? AliasSet::WasmStructOutlineDataArea
+ : AliasSet::WasmStructInlineDataArea;
+
+ return writeGcValueAtBasePlusOffset(lineOrBytecode, fieldType, structObject,
+ fieldAliasSet, value, base, areaOffset,
+ needsTrapInfo, preBarrierKind);
+ }
+
+ // 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 StructField& field, FieldWideningOp wideningOp,
+ MDefinition* structObject) {
+ FieldType fieldType = field.type;
+ uint32_t fieldOffset = field.offset;
+
+ bool areaIsOutline;
+ uint32_t areaOffset;
+ WasmStructObject::fieldOffsetToAreaAndOffset(fieldType, fieldOffset,
+ &areaIsOutline, &areaOffset);
+
+ // Make `base` point at the first byte of either the struct object as a
+ // whole or of the out-of-line data area. And adjust `areaOffset`
+ // accordingly.
+ MDefinition* base;
+ bool needsTrapInfo;
+ if (areaIsOutline) {
+ auto* loadOOLptr = MWasmLoadField::New(
+ alloc(), structObject, WasmStructObject::offsetOfOutlineData(),
+ MIRType::Pointer, MWideningOp::None,
+ AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
+ mozilla::Some(getTrapSiteInfo()));
+ if (!loadOOLptr) {
+ return nullptr;
+ }
+ curBlock_->add(loadOOLptr);
+ base = loadOOLptr;
+ needsTrapInfo = false;
+ } else {
+ base = structObject;
+ needsTrapInfo = true;
+ areaOffset += WasmStructObject::offsetOfInlineData();
+ }
+ // The transaction is to happen at `base + areaOffset`, so to speak.
+ // After this point we must ignore `fieldOffset`.
+
+ // The alias set denoting the field's location, although lacking a
+ // Load-vs-Store indication at this point.
+ AliasSet::Flag fieldAliasSet = areaIsOutline
+ ? AliasSet::WasmStructOutlineDataArea
+ : AliasSet::WasmStructInlineDataArea;
+
+ return readGcValueAtBasePlusOffset(fieldType, wideningOp, structObject,
+ fieldAliasSet, base, areaOffset,
+ 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;
+ }
+
+ // Compute `base + index * scale`, for both 32- and 64-bit targets. For the
+ // convenience of callers, on a 64-bit target, `index` and `scale` can
+ // (independently) be either Int32 or Int64; in the former case they will be
+ // zero-extended before the multiplication, so that both the multiplication
+ // and addition are done at the target word size.
+ [[nodiscard]] MDefinition* computeBasePlusScaledIndex(MDefinition* base,
+ MDefinition* scale,
+ MDefinition* index) {
+ // On a 32-bit target, require:
+ // base : Int32 (== TargetWordMIRType())
+ // index, scale : Int32
+ // Calculate base +32 (index *32 scale)
+ //
+ // On a 64-bit target, require:
+ // base : Int64 (== TargetWordMIRType())
+ // index, scale: either Int32 or Int64 (any combination is OK)
+ // Calculate base +64 (u-widen to 64(index)) *64 (u-widen to 64(scale))
+ //
+ // Final result type is the same as that of `base`.
+
+ MOZ_ASSERT(base->type() == TargetWordMIRType());
+
+ // Widen `index` if necessary, producing `indexW`.
+ MDefinition* indexW = unsignedWidenToTargetWord(index);
+ if (!indexW) {
+ return nullptr;
+ }
+ // Widen `scale` if necessary, producing `scaleW`.
+ MDefinition* scaleW = unsignedWidenToTargetWord(scale);
+ if (!scaleW) {
+ return nullptr;
+ }
+ // Compute `scaledIndex = indexW * scaleW`.
+ MIRType targetWordType = TargetWordMIRType();
+ bool targetIs64 = targetWordType == MIRType::Int64;
+ MMul* scaledIndex =
+ MMul::NewWasm(alloc(), indexW, scaleW, targetWordType,
+ targetIs64 ? MMul::Mode::Normal : MMul::Mode::Integer,
+ /*mustPreserveNan=*/false);
+ if (!scaledIndex) {
+ return nullptr;
+ }
+ // Compute `result = base + scaledIndex`.
+ curBlock_->add(scaledIndex);
+ MAdd* result = MAdd::NewWasm(alloc(), base, scaledIndex, targetWordType);
+ if (!result) {
+ return nullptr;
+ }
+ curBlock_->add(result);
+ return result;
+ }
+
+ /********************************************** 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::RefOrNull);
+
+ auto* numElements = MWasmLoadField::New(
+ alloc(), arrayObject, WasmArrayObject::offsetOfNumElements(),
+ MIRType::Int32, MWideningOp::None,
+ AliasSet::Load(AliasSet::WasmArrayNumElements),
+ mozilla::Some(getTrapSiteInfo()));
+ 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::RefOrNull);
+
+ auto* data = MWasmLoadField::New(
+ alloc(), arrayObject, WasmArrayObject::offsetOfData(),
+ TargetWordMIRType(), MWideningOp::None,
+ AliasSet::Load(AliasSet::WasmArrayDataPointer),
+ mozilla::Some(getTrapSiteInfo()));
+ 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 call `Instance::arrayNew`,
+ // producing an array with the relevant type and size and initialized with
+ // `typeIndex`s default value.
+ [[nodiscard]] MDefinition* createDefaultInitializedArrayObject(
+ uint32_t lineOrBytecode, uint32_t typeIndex, MDefinition* numElements) {
+ // Get the type definition for the array as a whole.
+ MDefinition* typeDefData = loadTypeDefInstanceData(typeIndex);
+ if (!typeDefData) {
+ return nullptr;
+ }
+
+ // Create call: arrayObject = Instance::arrayNew(numElements, typeDefData)
+ // If the requested size exceeds MaxArrayPayloadBytes, the MIR generated
+ // by this call will trap.
+ MDefinition* arrayObject;
+ if (!emitInstanceCall2(lineOrBytecode, SASigArrayNew, numElements,
+ typeDefData, &arrayObject)) {
+ return nullptr;
+ }
+
+ return arrayObject;
+ }
+
+ [[nodiscard]] MDefinition* createUninitializedArrayObject(
+ uint32_t lineOrBytecode, uint32_t typeIndex, MDefinition* numElements) {
+ // Get the type definition for the array as a whole.
+ MDefinition* typeDefData = loadTypeDefInstanceData(typeIndex);
+ if (!typeDefData) {
+ return nullptr;
+ }
+
+ // Create call: arrayObject = Instance::arrayNewUninit(numElements,
+ // typeDefData) If the requested size exceeds MaxArrayPayloadBytes, the MIR
+ // generated by this call will trap.
+ MDefinition* arrayObject;
+ if (!emitInstanceCall2(lineOrBytecode, SASigArrayNewUninit, numElements,
+ typeDefData, &arrayObject)) {
+ return nullptr;
+ }
+
+ 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::RefOrNull);
+ 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, bytecodeOffset(),
+ MWasmBoundsCheck::Target::Unknown);
+ if (!boundsCheck) {
+ return nullptr;
+ }
+ curBlock_->add(boundsCheck);
+
+ // Get the address of the first byte of the (OOL) data area.
+ return getWasmArrayObjectData(arrayObject);
+ }
+
+ // This routine generates all MIR required for `array.new`. The returned
+ // value is for the newly created array.
+ [[nodiscard]] MDefinition* createArrayNewCallAndLoop(uint32_t lineOrBytecode,
+ uint32_t typeIndex,
+ MDefinition* numElements,
+ MDefinition* fillValue) {
+ const ArrayType& arrayType = (*moduleEnv_.types)[typeIndex].arrayType();
+
+ // Create the array object, default-initialized.
+ MDefinition* arrayObject =
+ createUninitializedArrayObject(lineOrBytecode, typeIndex, numElements);
+ if (!arrayObject) {
+ return nullptr;
+ }
+
+ mozilla::DebugOnly<MIRType> fillValueMIRType = fillValue->type();
+ FieldType fillValueFieldType = arrayType.elementType_;
+ MOZ_ASSERT(fillValueFieldType.widenToValType().toMIRType() ==
+ fillValueMIRType);
+
+ uint32_t elemSize = fillValueFieldType.size();
+ MOZ_ASSERT(elemSize >= 1 && elemSize <= 16);
+
+ // Make `base` point at the first byte of the (OOL) data area.
+ MDefinition* base = getWasmArrayObjectData(arrayObject);
+ if (!base) {
+ return nullptr;
+ }
+
+ // We have:
+ // base : TargetWord
+ // numElements : Int32
+ // fillValue : <any FieldType>
+ // $elemSize = arrayType.elementType_.size(); 1, 2, 4, 8 or 16
+ //
+ // Generate MIR:
+ // <in current block>
+ // limit : TargetWord = base + nElems * elemSize
+ // if (limit == base) goto after; // skip loop if trip count == 0
+ // // optimisation (not done): skip loop if fill value == 0
+ // loop:
+ // ptrPhi = phi(base, ptrNext)
+ // *ptrPhi = fillValue
+ // ptrNext = ptrPhi + $elemSize
+ // if (ptrNext <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 nullptr;
+ }
+ MBasicBlock* afterBlock;
+ if (!newBlock(loopBlock, &afterBlock)) {
+ return nullptr;
+ }
+
+ // ==== Fill in the remainder of the block preceding the loop ====
+ MDefinition* elemSizeDef = constantTargetWord(intptr_t(elemSize));
+ if (!elemSizeDef) {
+ return nullptr;
+ }
+
+ MDefinition* limit =
+ computeBasePlusScaledIndex(base, elemSizeDef, numElements);
+ if (!limit) {
+ return nullptr;
+ }
+
+ // Use JSOp::StrictEq, not ::Eq, so that 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, base, JSOp::StrictEq,
+ targetIs64Bit() ? MCompare::Compare_UInt64 : MCompare::Compare_UInt32);
+ if (!limitEqualsBase) {
+ return nullptr;
+ }
+ MTest* skipIfLimitEqualsBase =
+ MTest::New(alloc(), limitEqualsBase, afterBlock, loopBlock);
+ if (!skipIfLimitEqualsBase) {
+ return nullptr;
+ }
+ curBlock_->end(skipIfLimitEqualsBase);
+ if (!afterBlock->addPredecessor(alloc(), curBlock_)) {
+ return nullptr;
+ }
+ // Optimisation opportunity: if the fill value is zero, maybe we should
+ // likewise skip over the initialisation loop entirely (and, if the zero
+ // value is visible at JIT time, the loop will be removed). For the
+ // reftyped case, that would be a big win since each iteration requires a
+ // call to the post-write barrier routine.
+
+ // ==== Fill in the loop block as best we can ====
+ curBlock_ = loopBlock;
+ MPhi* ptrPhi = MPhi::New(alloc(), TargetWordMIRType());
+ if (!ptrPhi) {
+ return nullptr;
+ }
+ if (!ptrPhi->reserveLength(2)) {
+ return nullptr;
+ }
+ ptrPhi->addInput(base);
+ curBlock_->addPhi(ptrPhi);
+ curBlock_->setLoopDepth(loopDepth_ + 1);
+
+ // Because we have the exact address to hand, use
+ // `writeGcValueAtBasePlusOffset` rather than
+ // `writeGcValueAtBasePlusScaledIndex` to do the store.
+ if (!writeGcValueAtBasePlusOffset(
+ lineOrBytecode, fillValueFieldType, arrayObject,
+ AliasSet::WasmArrayDataArea, fillValue, ptrPhi, /*offset=*/0,
+ /*needsTrapInfo=*/false, WasmPreBarrierKind::None)) {
+ return nullptr;
+ }
+
+ auto* ptrNext =
+ MAdd::NewWasm(alloc(), ptrPhi, elemSizeDef, TargetWordMIRType());
+ if (!ptrNext) {
+ return nullptr;
+ }
+ curBlock_->add(ptrNext);
+ ptrPhi->addInput(ptrNext);
+
+ MDefinition* ptrNextLtuLimit = compare(
+ ptrNext, limit, JSOp::Lt,
+ targetIs64Bit() ? MCompare::Compare_UInt64 : MCompare::Compare_UInt32);
+ if (!ptrNextLtuLimit) {
+ return nullptr;
+ }
+ auto* continueIfPtrNextLtuLimit =
+ MTest::New(alloc(), ptrNextLtuLimit, loopBlock, afterBlock);
+ if (!continueIfPtrNextLtuLimit) {
+ return nullptr;
+ }
+ curBlock_->end(continueIfPtrNextLtuLimit);
+ if (!loopBlock->addPredecessor(alloc(), loopBlock)) {
+ return nullptr;
+ }
+ // ==== Loop block completed ====
+
+ curBlock_ = afterBlock;
+ return arrayObject;
+ }
+
+ /*********************************************** WasmGC: other helpers ***/
+
+ // Generate MIR that causes a trap of kind `trapKind` if `arg` is zero.
+ // Currently `arg` may only be a MIRType::Int32, but that requirement could
+ // be relaxed if needed in future.
+ [[nodiscard]] bool trapIfZero(wasm::Trap trapKind, MDefinition* arg) {
+ MOZ_ASSERT(arg->type() == MIRType::Int32);
+
+ MBasicBlock* trapBlock = nullptr;
+ if (!newBlock(curBlock_, &trapBlock)) {
+ return false;
+ }
+
+ auto* trap = MWasmTrap::New(alloc(), trapKind, bytecodeOffset());
+ if (!trap) {
+ return false;
+ }
+ trapBlock->end(trap);
+
+ MBasicBlock* joinBlock = nullptr;
+ if (!newBlock(curBlock_, &joinBlock)) {
+ return false;
+ }
+
+ auto* test = MTest::New(alloc(), arg, joinBlock, trapBlock);
+ if (!test) {
+ return false;
+ }
+ curBlock_->end(test);
+ curBlock_ = joinBlock;
+ return true;
+ }
+
+ [[nodiscard]] MDefinition* isGcObjectSubtypeOf(MDefinition* object,
+ RefType sourceType,
+ RefType destType) {
+ MInstruction* isSubTypeOf = nullptr;
+ if (destType.isTypeRef()) {
+ uint32_t typeIndex = moduleEnv_.types->indexOf(*destType.typeDef());
+ MDefinition* superSuperTypeVector = loadSuperTypeVector(typeIndex);
+ isSubTypeOf = MWasmGcObjectIsSubtypeOfConcrete::New(
+ alloc(), object, superSuperTypeVector, sourceType, destType);
+ } else {
+ isSubTypeOf = MWasmGcObjectIsSubtypeOfAbstract::New(alloc(), object,
+ sourceType, destType);
+ }
+ MOZ_ASSERT(isSubTypeOf);
+
+ curBlock_->add(isSubTypeOf);
+ return isSubTypeOf;
+ }
+
+ // Generate MIR that attempts to downcast `ref` to `castToTypeDef`. If the
+ // downcast 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]] bool refCast(MDefinition* ref, RefType sourceType,
+ RefType destType) {
+ MDefinition* success = isGcObjectSubtypeOf(ref, sourceType, destType);
+ if (!success) {
+ return false;
+ }
+
+ // Trap if `success` is zero. If it's nonzero, we have established that
+ // `ref <: castToTypeDef`.
+ return trapIfZero(wasm::Trap::BadCast, success);
+ }
+
+ // Generate MIR that computes a boolean value indicating whether or not it
+ // is possible to downcast `ref` to `destType`.
+ [[nodiscard]] MDefinition* refTest(MDefinition* ref, RefType sourceType,
+ RefType destType) {
+ return isGcObjectSubtypeOf(ref, sourceType, destType);
+ }
+
+ // 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) {
+ 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::RefOrNull);
+
+ MDefinition* success = isGcObjectSubtypeOf(ref, sourceType, destType);
+ if (!success) {
+ return false;
+ }
+
+ MTest* test;
+ if (onSuccess) {
+ test = MTest::New(alloc(), success, nullptr, fallthroughBlock);
+ if (!test || !addControlFlowPatch(test, labelRelativeDepth,
+ MTest::TrueBranchIndex)) {
+ return false;
+ }
+ } else {
+ test = MTest::New(alloc(), success, fallthroughBlock, nullptr);
+ if (!test || !addControlFlowPatch(test, labelRelativeDepth,
+ MTest::FalseBranchIndex)) {
+ 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::RefOrNull);
+
+ MGoto* jump = MGoto::New(alloc(), fallthroughBlock);
+ if (!jump) {
+ return false;
+ }
+ if (!pushDefs(values)) {
+ return false;
+ }
+
+ curBlock_->end(jump);
+ curBlock_ = fallthroughBlock;
+ return true;
+ }
+
+ /************************************************************ DECODING ***/
+
+ // AsmJS adds a line number to `callSiteLineNums` for certain operations that
+ // are represented by a JS call, such as math builtins. We use these line
+ // numbers when calling builtins. This method will read from
+ // `callSiteLineNums` when we are using AsmJS, or else return the current
+ // bytecode offset.
+ //
+ // This method MUST be called from opcodes that AsmJS will emit a call site
+ // line number for, or else the arrays will get out of sync. Other opcodes
+ // must use `readBytecodeOffset` below.
+ uint32_t readCallSiteLineOrBytecode() {
+ if (!func_.callSiteLineNums.empty()) {
+ return func_.callSiteLineNums[lastReadCallSite_++];
+ }
+ return iter_.lastOpcodeOffset();
+ }
+
+ // Return the current bytecode offset.
+ uint32_t readBytecodeOffset() { return iter_.lastOpcodeOffset(); }
+
+ TrapSiteInfo getTrapSiteInfo() {
+ return TrapSiteInfo(wasm::BytecodeOffset(readBytecodeOffset()));
+ }
+
+#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(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 >= blockPatches_.length() || blockPatches_[absolute].empty()) {
+ return inDeadCode() || popPushedDefs(defs);
+ }
+
+ ControlFlowPatchVector& patches = blockPatches_[absolute];
+ MControlInstruction* ins = patches[0].ins;
+ MBasicBlock* pred = ins->block();
+
+ MBasicBlock* join = nullptr;
+ if (!newBlock(pred, &join)) {
+ return false;
+ }
+
+ 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;
+ }
+};
+
+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<MWasmBuiltinTruncateToInt32>(
+ MDefinition* op) {
+ if (inDeadCode()) {
+ return nullptr;
+ }
+ auto* ins = MWasmBuiltinTruncateToInt32::New(alloc(), op, instancePointer_,
+ bytecodeOffset());
+ 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;
+}
+
+} // end anonymous namespace
+
+static bool EmitI32Const(FunctionCompiler& f) {
+ int32_t i32;
+ if (!f.iter().readI32Const(&i32)) {
+ return false;
+ }
+
+ f.iter().setResult(f.constantI32(i32));
+ return true;
+}
+
+static bool EmitI64Const(FunctionCompiler& f) {
+ int64_t i64;
+ if (!f.iter().readI64Const(&i64)) {
+ return false;
+ }
+
+ f.iter().setResult(f.constantI64(i64));
+ return true;
+}
+
+static bool EmitF32Const(FunctionCompiler& f) {
+ float f32;
+ if (!f.iter().readF32Const(&f32)) {
+ return false;
+ }
+
+ f.iter().setResult(f.constantF32(f32));
+ return true;
+}
+
+static bool EmitF64Const(FunctionCompiler& f) {
+ double f64;
+ if (!f.iter().readF64Const(&f64)) {
+ return false;
+ }
+
+ f.iter().setResult(f.constantF64(f64));
+ return true;
+}
+
+static bool EmitBlock(FunctionCompiler& f) {
+ ResultType params;
+ return f.iter().readBlock(&params) && f.startBlock();
+}
+
+static bool EmitLoop(FunctionCompiler& f) {
+ ResultType params;
+ if (!f.iter().readLoop(&params)) {
+ return false;
+ }
+
+ MBasicBlock* loopHeader;
+ if (!f.startLoop(&loopHeader, params.length())) {
+ return false;
+ }
+
+ f.addInterruptCheck();
+
+ f.iter().controlItem().setBlock(loopHeader);
+ return true;
+}
+
+static bool EmitIf(FunctionCompiler& f) {
+ ResultType params;
+ MDefinition* condition = nullptr;
+ if (!f.iter().readIf(&params, &condition)) {
+ return false;
+ }
+
+ MBasicBlock* elseBlock;
+ if (!f.branchAndStartThen(condition, &elseBlock)) {
+ return false;
+ }
+
+ f.iter().controlItem().setBlock(elseBlock);
+ return true;
+}
+
+static bool EmitElse(FunctionCompiler& f) {
+ ResultType paramType;
+ ResultType resultType;
+ DefVector thenValues;
+ if (!f.iter().readElse(&paramType, &resultType, &thenValues)) {
+ return false;
+ }
+
+ if (!f.pushDefs(thenValues)) {
+ return false;
+ }
+
+ Control& control = f.iter().controlItem();
+ if (!f.switchToElse(control.block, &control.block)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool EmitEnd(FunctionCompiler& f) {
+ LabelKind kind;
+ ResultType type;
+ DefVector preJoinDefs;
+ DefVector resultsForEmptyElse;
+ if (!f.iter().readEnd(&kind, &type, &preJoinDefs, &resultsForEmptyElse)) {
+ return false;
+ }
+
+ Control& control = f.iter().controlItem();
+ MBasicBlock* block = control.block;
+
+ if (!f.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:
+ if (!f.emitBodyDelegateThrowPad(control)) {
+ return false;
+ }
+ if (!f.finishBlock(&postJoinDefs)) {
+ return false;
+ }
+ if (!f.returnValues(postJoinDefs)) {
+ return false;
+ }
+ f.iter().popEnd();
+ MOZ_ASSERT(f.iter().controlStackEmpty());
+ return f.iter().endFunction(f.iter().end());
+ case LabelKind::Block:
+ if (!f.finishBlock(&postJoinDefs)) {
+ return false;
+ }
+ f.iter().popEnd();
+ break;
+ case LabelKind::Loop:
+ if (!f.closeLoop(block, &postJoinDefs)) {
+ return false;
+ }
+ f.iter().popEnd();
+ break;
+ case LabelKind::Then: {
+ // If we didn't see an Else, create a trivial else block so that we create
+ // a diamond anyway, to preserve Ion invariants.
+ if (!f.switchToElse(block, &block)) {
+ return false;
+ }
+
+ if (!f.pushDefs(resultsForEmptyElse)) {
+ return false;
+ }
+
+ if (!f.joinIfElse(block, &postJoinDefs)) {
+ return false;
+ }
+ f.iter().popEnd();
+ break;
+ }
+ case LabelKind::Else:
+ if (!f.joinIfElse(block, &postJoinDefs)) {
+ return false;
+ }
+ f.iter().popEnd();
+ break;
+ case LabelKind::Try:
+ case LabelKind::Catch:
+ case LabelKind::CatchAll:
+ if (!f.finishTryCatch(kind, control, &postJoinDefs)) {
+ return false;
+ }
+ f.iter().popEnd();
+ break;
+ }
+
+ MOZ_ASSERT_IF(!f.inDeadCode(), postJoinDefs.length() == type.length());
+ f.iter().setResults(postJoinDefs.length(), postJoinDefs);
+
+ return true;
+}
+
+static bool EmitBr(FunctionCompiler& f) {
+ uint32_t relativeDepth;
+ ResultType type;
+ DefVector values;
+ if (!f.iter().readBr(&relativeDepth, &type, &values)) {
+ return false;
+ }
+
+ return f.br(relativeDepth, values);
+}
+
+static bool EmitBrIf(FunctionCompiler& f) {
+ uint32_t relativeDepth;
+ ResultType type;
+ DefVector values;
+ MDefinition* condition;
+ if (!f.iter().readBrIf(&relativeDepth, &type, &values, &condition)) {
+ return false;
+ }
+
+ return f.brIf(relativeDepth, values, condition);
+}
+
+static bool EmitBrTable(FunctionCompiler& f) {
+ Uint32Vector depths;
+ uint32_t defaultDepth;
+ ResultType branchValueType;
+ DefVector branchValues;
+ MDefinition* index;
+ if (!f.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 f.br(defaultDepth, branchValues);
+ }
+
+ return f.brTable(index, defaultDepth, depths, branchValues);
+}
+
+static bool EmitReturn(FunctionCompiler& f) {
+ DefVector values;
+ if (!f.iter().readReturn(&values)) {
+ return false;
+ }
+
+ return f.returnValues(values);
+}
+
+static bool EmitUnreachable(FunctionCompiler& f) {
+ if (!f.iter().readUnreachable()) {
+ return false;
+ }
+
+ f.unreachableTrap();
+ return true;
+}
+
+static bool EmitTry(FunctionCompiler& f) {
+ ResultType params;
+ if (!f.iter().readTry(&params)) {
+ return false;
+ }
+
+ MBasicBlock* curBlock = nullptr;
+ if (!f.startTry(&curBlock)) {
+ return false;
+ }
+
+ f.iter().controlItem().setBlock(curBlock);
+ return true;
+}
+
+static bool EmitCatch(FunctionCompiler& f) {
+ LabelKind kind;
+ uint32_t tagIndex;
+ ResultType paramType, resultType;
+ DefVector tryValues;
+ if (!f.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 (!f.pushDefs(tryValues)) {
+ return false;
+ }
+
+ return f.switchToCatch(f.iter().controlItem(), kind, tagIndex);
+}
+
+static bool EmitCatchAll(FunctionCompiler& f) {
+ LabelKind kind;
+ ResultType paramType, resultType;
+ DefVector tryValues;
+ if (!f.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 (!f.pushDefs(tryValues)) {
+ return false;
+ }
+
+ return f.switchToCatch(f.iter().controlItem(), kind, CatchAllIndex);
+}
+
+static bool EmitDelegate(FunctionCompiler& f) {
+ uint32_t relativeDepth;
+ ResultType resultType;
+ DefVector tryValues;
+ if (!f.iter().readDelegate(&relativeDepth, &resultType, &tryValues)) {
+ return false;
+ }
+
+ Control& control = f.iter().controlItem();
+ MBasicBlock* block = control.block;
+
+ // 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& delegatePadPatches = control.tryPadPatches;
+ if (!f.delegatePadPatches(delegatePadPatches, relativeDepth)) {
+ return false;
+ }
+ }
+ f.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 (!f.pushDefs(tryValues)) {
+ return false;
+ }
+ DefVector postJoinDefs;
+ if (!f.finishBlock(&postJoinDefs)) {
+ return false;
+ }
+ MOZ_ASSERT_IF(!f.inDeadCode(), postJoinDefs.length() == resultType.length());
+ f.iter().setResults(postJoinDefs.length(), postJoinDefs);
+
+ return true;
+}
+
+static bool EmitThrow(FunctionCompiler& f) {
+ uint32_t tagIndex;
+ DefVector argValues;
+ if (!f.iter().readThrow(&tagIndex, &argValues)) {
+ return false;
+ }
+
+ return f.emitThrow(tagIndex, argValues);
+}
+
+static bool EmitRethrow(FunctionCompiler& f) {
+ uint32_t relativeDepth;
+ if (!f.iter().readRethrow(&relativeDepth)) {
+ return false;
+ }
+
+ return f.emitRethrow(relativeDepth);
+}
+
+static bool EmitCallArgs(FunctionCompiler& f, const FuncType& funcType,
+ const DefVector& args, CallCompileState* call) {
+ for (size_t i = 0, n = funcType.args().length(); i < n; ++i) {
+ if (!f.mirGen().ensureBallast()) {
+ return false;
+ }
+ if (!f.passArg(args[i], funcType.args()[i], call)) {
+ return false;
+ }
+ }
+
+ ResultType resultType = ResultType::Vector(funcType.results());
+ if (!f.passStackResultAreaCallArg(resultType, call)) {
+ return false;
+ }
+
+ return f.finishCall(call);
+}
+
+static bool EmitCall(FunctionCompiler& f, bool asmJSFuncDef) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t funcIndex;
+ DefVector args;
+ if (asmJSFuncDef) {
+ if (!f.iter().readOldCallDirect(f.moduleEnv().numFuncImports, &funcIndex,
+ &args)) {
+ return false;
+ }
+ } else {
+ if (!f.iter().readCall(&funcIndex, &args)) {
+ return false;
+ }
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ const FuncType& funcType = *f.moduleEnv().funcs[funcIndex].type;
+
+ CallCompileState call;
+ if (!EmitCallArgs(f, funcType, args, &call)) {
+ return false;
+ }
+
+ DefVector results;
+ if (f.moduleEnv().funcIsImport(funcIndex)) {
+ uint32_t instanceDataOffset =
+ f.moduleEnv().offsetOfFuncImportInstanceData(funcIndex);
+ if (!f.callImport(instanceDataOffset, lineOrBytecode, call, funcType,
+ &results)) {
+ return false;
+ }
+ } else {
+ if (!f.callDirect(funcType, funcIndex, lineOrBytecode, call, &results)) {
+ return false;
+ }
+ }
+
+ f.iter().setResults(results.length(), results);
+ return true;
+}
+
+static bool EmitCallIndirect(FunctionCompiler& f, bool oldStyle) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t funcTypeIndex;
+ uint32_t tableIndex;
+ MDefinition* callee;
+ DefVector args;
+ if (oldStyle) {
+ tableIndex = 0;
+ if (!f.iter().readOldCallIndirect(&funcTypeIndex, &callee, &args)) {
+ return false;
+ }
+ } else {
+ if (!f.iter().readCallIndirect(&funcTypeIndex, &tableIndex, &callee,
+ &args)) {
+ return false;
+ }
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ const FuncType& funcType = (*f.moduleEnv().types)[funcTypeIndex].funcType();
+
+ CallCompileState call;
+ if (!EmitCallArgs(f, funcType, args, &call)) {
+ return false;
+ }
+
+ DefVector results;
+ if (!f.callIndirect(funcTypeIndex, tableIndex, callee, lineOrBytecode, call,
+ &results)) {
+ return false;
+ }
+
+ f.iter().setResults(results.length(), results);
+ return true;
+}
+
+static bool EmitGetLocal(FunctionCompiler& f) {
+ uint32_t id;
+ if (!f.iter().readGetLocal(f.locals(), &id)) {
+ return false;
+ }
+
+ f.iter().setResult(f.getLocalDef(id));
+ return true;
+}
+
+static bool EmitSetLocal(FunctionCompiler& f) {
+ uint32_t id;
+ MDefinition* value;
+ if (!f.iter().readSetLocal(f.locals(), &id, &value)) {
+ return false;
+ }
+
+ f.assign(id, value);
+ return true;
+}
+
+static bool EmitTeeLocal(FunctionCompiler& f) {
+ uint32_t id;
+ MDefinition* value;
+ if (!f.iter().readTeeLocal(f.locals(), &id, &value)) {
+ return false;
+ }
+
+ f.assign(id, value);
+ return true;
+}
+
+static bool EmitGetGlobal(FunctionCompiler& f) {
+ uint32_t id;
+ if (!f.iter().readGetGlobal(&id)) {
+ return false;
+ }
+
+ const GlobalDesc& global = f.moduleEnv().globals[id];
+ if (!global.isConstant()) {
+ f.iter().setResult(f.loadGlobalVar(global.offset(), !global.isMutable(),
+ global.isIndirect(),
+ global.type().toMIRType()));
+ return true;
+ }
+
+ LitVal value = global.constantValue();
+
+ MDefinition* result;
+ switch (value.type().kind()) {
+ case ValType::I32:
+ result = f.constantI32(int32_t(value.i32()));
+ break;
+ case ValType::I64:
+ result = f.constantI64(int64_t(value.i64()));
+ break;
+ case ValType::F32:
+ result = f.constantF32(value.f32());
+ break;
+ case ValType::F64:
+ result = f.constantF64(value.f64());
+ break;
+ case ValType::V128:
+#ifdef ENABLE_WASM_SIMD
+ result = f.constantV128(value.v128());
+ break;
+#else
+ return f.iter().fail("Ion has no SIMD support yet");
+#endif
+ case ValType::Ref:
+ MOZ_ASSERT(value.ref().isNull());
+ result = f.constantNullRef();
+ break;
+ default:
+ MOZ_CRASH("unexpected type in EmitGetGlobal");
+ }
+
+ f.iter().setResult(result);
+ return true;
+}
+
+static bool EmitSetGlobal(FunctionCompiler& f) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ uint32_t id;
+ MDefinition* value;
+ if (!f.iter().readSetGlobal(&id, &value)) {
+ return false;
+ }
+
+ const GlobalDesc& global = f.moduleEnv().globals[id];
+ MOZ_ASSERT(global.isMutable());
+ return f.storeGlobalVar(bytecodeOffset, global.offset(), global.isIndirect(),
+ value);
+}
+
+static bool EmitTeeGlobal(FunctionCompiler& f) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ uint32_t id;
+ MDefinition* value;
+ if (!f.iter().readTeeGlobal(&id, &value)) {
+ return false;
+ }
+
+ const GlobalDesc& global = f.moduleEnv().globals[id];
+ MOZ_ASSERT(global.isMutable());
+
+ return f.storeGlobalVar(bytecodeOffset, global.offset(), global.isIndirect(),
+ value);
+}
+
+template <typename MIRClass>
+static bool EmitUnary(FunctionCompiler& f, ValType operandType) {
+ MDefinition* input;
+ if (!f.iter().readUnary(operandType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.unary<MIRClass>(input));
+ return true;
+}
+
+template <typename MIRClass>
+static bool EmitConversion(FunctionCompiler& f, ValType operandType,
+ ValType resultType) {
+ MDefinition* input;
+ if (!f.iter().readConversion(operandType, resultType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.unary<MIRClass>(input));
+ return true;
+}
+
+template <typename MIRClass>
+static bool EmitUnaryWithType(FunctionCompiler& f, ValType operandType,
+ MIRType mirType) {
+ MDefinition* input;
+ if (!f.iter().readUnary(operandType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.unary<MIRClass>(input, mirType));
+ return true;
+}
+
+template <typename MIRClass>
+static bool EmitConversionWithType(FunctionCompiler& f, ValType operandType,
+ ValType resultType, MIRType mirType) {
+ MDefinition* input;
+ if (!f.iter().readConversion(operandType, resultType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.unary<MIRClass>(input, mirType));
+ return true;
+}
+
+static bool EmitTruncate(FunctionCompiler& f, ValType operandType,
+ ValType resultType, bool isUnsigned,
+ bool isSaturating) {
+ MDefinition* input = nullptr;
+ if (!f.iter().readConversion(operandType, resultType, &input)) {
+ return false;
+ }
+
+ TruncFlags flags = 0;
+ if (isUnsigned) {
+ flags |= TRUNC_UNSIGNED;
+ }
+ if (isSaturating) {
+ flags |= TRUNC_SATURATING;
+ }
+ if (resultType == ValType::I32) {
+ if (f.moduleEnv().isAsmJS()) {
+ if (input && (input->type() == MIRType::Double ||
+ input->type() == MIRType::Float32)) {
+ f.iter().setResult(f.unary<MWasmBuiltinTruncateToInt32>(input));
+ } else {
+ f.iter().setResult(f.unary<MTruncateToInt32>(input));
+ }
+ } else {
+ f.iter().setResult(f.truncate<MWasmTruncateToInt32>(input, flags));
+ }
+ } else {
+ MOZ_ASSERT(resultType == ValType::I64);
+ MOZ_ASSERT(!f.moduleEnv().isAsmJS());
+#if defined(JS_CODEGEN_ARM)
+ f.iter().setResult(f.truncateWithInstance(input, flags));
+#else
+ f.iter().setResult(f.truncate<MWasmTruncateToInt64>(input, flags));
+#endif
+ }
+ return true;
+}
+
+static bool EmitSignExtend(FunctionCompiler& f, uint32_t srcSize,
+ uint32_t targetSize) {
+ MDefinition* input;
+ ValType type = targetSize == 4 ? ValType::I32 : ValType::I64;
+ if (!f.iter().readConversion(type, type, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.signExtend(input, srcSize, targetSize));
+ return true;
+}
+
+static bool EmitExtendI32(FunctionCompiler& f, bool isUnsigned) {
+ MDefinition* input;
+ if (!f.iter().readConversion(ValType::I32, ValType::I64, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.extendI32(input, isUnsigned));
+ return true;
+}
+
+static bool EmitConvertI64ToFloatingPoint(FunctionCompiler& f,
+ ValType resultType, MIRType mirType,
+ bool isUnsigned) {
+ MDefinition* input;
+ if (!f.iter().readConversion(ValType::I64, resultType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.convertI64ToFloatingPoint(input, mirType, isUnsigned));
+ return true;
+}
+
+static bool EmitReinterpret(FunctionCompiler& f, ValType resultType,
+ ValType operandType, MIRType mirType) {
+ MDefinition* input;
+ if (!f.iter().readConversion(operandType, resultType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.unary<MWasmReinterpret>(input, mirType));
+ return true;
+}
+
+static bool EmitAdd(FunctionCompiler& f, ValType type, MIRType mirType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(type, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.add(lhs, rhs, mirType));
+ return true;
+}
+
+static bool EmitSub(FunctionCompiler& f, ValType type, MIRType mirType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(type, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.sub(lhs, rhs, mirType));
+ return true;
+}
+
+static bool EmitRotate(FunctionCompiler& f, ValType type, bool isLeftRotation) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(type, &lhs, &rhs)) {
+ return false;
+ }
+
+ MDefinition* result = f.rotate(lhs, rhs, type.toMIRType(), isLeftRotation);
+ f.iter().setResult(result);
+ return true;
+}
+
+static bool EmitBitNot(FunctionCompiler& f, ValType operandType) {
+ MDefinition* input;
+ if (!f.iter().readUnary(operandType, &input)) {
+ return false;
+ }
+
+ f.iter().setResult(f.bitnot(input));
+ return true;
+}
+
+static bool EmitBitwiseAndOrXor(FunctionCompiler& f, ValType operandType,
+ MIRType mirType,
+ MWasmBinaryBitwise::SubOpcode subOpc) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.binary<MWasmBinaryBitwise>(lhs, rhs, mirType, subOpc));
+ return true;
+}
+
+template <typename MIRClass>
+static bool EmitShift(FunctionCompiler& f, ValType operandType,
+ MIRType mirType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.binary<MIRClass>(lhs, rhs, mirType));
+ return true;
+}
+
+static bool EmitUrsh(FunctionCompiler& f, ValType operandType,
+ MIRType mirType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.ursh(lhs, rhs, mirType));
+ return true;
+}
+
+static bool EmitMul(FunctionCompiler& f, ValType operandType, MIRType mirType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(
+ f.mul(lhs, rhs, mirType,
+ mirType == MIRType::Int32 ? MMul::Integer : MMul::Normal));
+ return true;
+}
+
+static bool EmitDiv(FunctionCompiler& f, ValType operandType, MIRType mirType,
+ bool isUnsigned) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.div(lhs, rhs, mirType, isUnsigned));
+ return true;
+}
+
+static bool EmitRem(FunctionCompiler& f, ValType operandType, MIRType mirType,
+ bool isUnsigned) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.mod(lhs, rhs, mirType, isUnsigned));
+ return true;
+}
+
+static bool EmitMinMax(FunctionCompiler& f, ValType operandType,
+ MIRType mirType, bool isMax) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.minMax(lhs, rhs, mirType, isMax));
+ return true;
+}
+
+static bool EmitCopySign(FunctionCompiler& f, ValType operandType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.binary<MCopySign>(lhs, rhs, operandType.toMIRType()));
+ return true;
+}
+
+static bool EmitComparison(FunctionCompiler& f, ValType operandType,
+ JSOp compareOp, MCompare::CompareType compareType) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readComparison(operandType, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.compare(lhs, rhs, compareOp, compareType));
+ return true;
+}
+
+static bool EmitSelect(FunctionCompiler& f, bool typed) {
+ StackType type;
+ MDefinition* trueValue;
+ MDefinition* falseValue;
+ MDefinition* condition;
+ if (!f.iter().readSelect(typed, &type, &trueValue, &falseValue, &condition)) {
+ return false;
+ }
+
+ f.iter().setResult(f.select(trueValue, falseValue, condition));
+ return true;
+}
+
+static bool EmitLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readLoad(type, Scalar::byteSize(viewType), &addr)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset,
+ f.bytecodeIfNotAsmJS());
+ auto* ins = f.load(addr.base, &access, type);
+ if (!f.inDeadCode() && !ins) {
+ return false;
+ }
+
+ f.iter().setResult(ins);
+ return true;
+}
+
+static bool EmitStore(FunctionCompiler& f, ValType resultType,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* value;
+ if (!f.iter().readStore(resultType, Scalar::byteSize(viewType), &addr,
+ &value)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset,
+ f.bytecodeIfNotAsmJS());
+
+ f.store(addr.base, &access, value);
+ return true;
+}
+
+static bool EmitTeeStore(FunctionCompiler& f, ValType resultType,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* value;
+ if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr,
+ &value)) {
+ return false;
+ }
+
+ MOZ_ASSERT(f.isMem32()); // asm.js opcode
+ MemoryAccessDesc access(viewType, addr.align, addr.offset,
+ f.bytecodeIfNotAsmJS());
+
+ f.store(addr.base, &access, value);
+ return true;
+}
+
+static bool EmitTeeStoreWithCoercion(FunctionCompiler& f, ValType resultType,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* value;
+ if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr,
+ &value)) {
+ return false;
+ }
+
+ if (resultType == ValType::F32 && viewType == Scalar::Float64) {
+ value = f.unary<MToDouble>(value);
+ } else if (resultType == ValType::F64 && viewType == Scalar::Float32) {
+ value = f.unary<MToFloat32>(value);
+ } else {
+ MOZ_CRASH("unexpected coerced store");
+ }
+
+ MOZ_ASSERT(f.isMem32()); // asm.js opcode
+ MemoryAccessDesc access(viewType, addr.align, addr.offset,
+ f.bytecodeIfNotAsmJS());
+
+ f.store(addr.base, &access, value);
+ return true;
+}
+
+static bool TryInlineUnaryBuiltin(FunctionCompiler& f, 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;
+ }
+
+ f.iter().setResult(f.nearbyInt(input, mode));
+ return true;
+}
+
+static bool EmitUnaryMathBuiltinCall(FunctionCompiler& f,
+ const SymbolicAddressSignature& callee) {
+ MOZ_ASSERT(callee.numArgs == 1);
+
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ MDefinition* input;
+ if (!f.iter().readUnary(ValType::fromMIRType(callee.argTypes[0]), &input)) {
+ return false;
+ }
+
+ if (TryInlineUnaryBuiltin(f, callee.identity, input)) {
+ return true;
+ }
+
+ CallCompileState call;
+ if (!f.passArg(input, callee.argTypes[0], &call)) {
+ return false;
+ }
+
+ if (!f.finishCall(&call)) {
+ return false;
+ }
+
+ MDefinition* def;
+ if (!f.builtinCall(callee, lineOrBytecode, call, &def)) {
+ return false;
+ }
+
+ f.iter().setResult(def);
+ return true;
+}
+
+static bool EmitBinaryMathBuiltinCall(FunctionCompiler& f,
+ const SymbolicAddressSignature& callee) {
+ MOZ_ASSERT(callee.numArgs == 2);
+ MOZ_ASSERT(callee.argTypes[0] == callee.argTypes[1]);
+
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ CallCompileState call;
+ MDefinition* lhs;
+ MDefinition* rhs;
+ // This call to readBinary assumes both operands have the same type.
+ if (!f.iter().readBinary(ValType::fromMIRType(callee.argTypes[0]), &lhs,
+ &rhs)) {
+ return false;
+ }
+
+ if (!f.passArg(lhs, callee.argTypes[0], &call)) {
+ return false;
+ }
+
+ if (!f.passArg(rhs, callee.argTypes[1], &call)) {
+ return false;
+ }
+
+ if (!f.finishCall(&call)) {
+ return false;
+ }
+
+ MDefinition* def;
+ if (!f.builtinCall(callee, lineOrBytecode, call, &def)) {
+ return false;
+ }
+
+ f.iter().setResult(def);
+ return true;
+}
+
+static bool EmitMemoryGrow(FunctionCompiler& f) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* delta;
+ if (!f.iter().readMemoryGrow(&delta)) {
+ return false;
+ }
+
+ const SymbolicAddressSignature& callee =
+ f.isNoMemOrMem32() ? SASigMemoryGrowM32 : SASigMemoryGrowM64;
+
+ MDefinition* ret;
+ if (!f.emitInstanceCall1(bytecodeOffset, callee, delta, &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitMemorySize(FunctionCompiler& f) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ if (!f.iter().readMemorySize()) {
+ return false;
+ }
+
+ const SymbolicAddressSignature& callee =
+ f.isNoMemOrMem32() ? SASigMemorySizeM32 : SASigMemorySizeM64;
+
+ MDefinition* ret;
+ if (!f.emitInstanceCall0(bytecodeOffset, callee, &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitAtomicCmpXchg(FunctionCompiler& f, ValType type,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* oldValue;
+ MDefinition* newValue;
+ if (!f.iter().readAtomicCmpXchg(&addr, type, byteSize(viewType), &oldValue,
+ &newValue)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(),
+ Synchronization::Full());
+ auto* ins =
+ f.atomicCompareExchangeHeap(addr.base, &access, type, oldValue, newValue);
+ if (!f.inDeadCode() && !ins) {
+ return false;
+ }
+
+ f.iter().setResult(ins);
+ return true;
+}
+
+static bool EmitAtomicLoad(FunctionCompiler& f, ValType type,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readAtomicLoad(&addr, type, byteSize(viewType))) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(),
+ Synchronization::Load());
+ auto* ins = f.load(addr.base, &access, type);
+ if (!f.inDeadCode() && !ins) {
+ return false;
+ }
+
+ f.iter().setResult(ins);
+ return true;
+}
+
+static bool EmitAtomicRMW(FunctionCompiler& f, ValType type,
+ Scalar::Type viewType, jit::AtomicOp op) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* value;
+ if (!f.iter().readAtomicRMW(&addr, type, byteSize(viewType), &value)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(),
+ Synchronization::Full());
+ auto* ins = f.atomicBinopHeap(op, addr.base, &access, type, value);
+ if (!f.inDeadCode() && !ins) {
+ return false;
+ }
+
+ f.iter().setResult(ins);
+ return true;
+}
+
+static bool EmitAtomicStore(FunctionCompiler& f, ValType type,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* value;
+ if (!f.iter().readAtomicStore(&addr, type, byteSize(viewType), &value)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(),
+ Synchronization::Store());
+ f.store(addr.base, &access, value);
+ return true;
+}
+
+static bool EmitWait(FunctionCompiler& f, ValType type, uint32_t byteSize) {
+ MOZ_ASSERT(type == ValType::I32 || type == ValType::I64);
+ MOZ_ASSERT(type.size() == byteSize);
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* expected;
+ MDefinition* timeout;
+ if (!f.iter().readWait(&addr, type, byteSize, &expected, &timeout)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(type == ValType::I32 ? Scalar::Int32 : Scalar::Int64,
+ addr.align, addr.offset, f.bytecodeOffset());
+ MDefinition* ptr = f.computeEffectiveAddress(addr.base, &access);
+ if (!f.inDeadCode() && !ptr) {
+ return false;
+ }
+
+ const SymbolicAddressSignature& callee =
+ f.isNoMemOrMem32()
+ ? (type == ValType::I32 ? SASigWaitI32M32 : SASigWaitI64M32)
+ : (type == ValType::I32 ? SASigWaitI32M64 : SASigWaitI64M64);
+ MOZ_ASSERT(type.toMIRType() == callee.argTypes[2]);
+
+ MDefinition* ret;
+ if (!f.emitInstanceCall3(bytecodeOffset, callee, ptr, expected, timeout,
+ &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitFence(FunctionCompiler& f) {
+ if (!f.iter().readFence()) {
+ return false;
+ }
+
+ f.fence();
+ return true;
+}
+
+static bool EmitWake(FunctionCompiler& f) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* count;
+ if (!f.iter().readWake(&addr, &count)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(Scalar::Int32, addr.align, addr.offset,
+ f.bytecodeOffset());
+ MDefinition* ptr = f.computeEffectiveAddress(addr.base, &access);
+ if (!f.inDeadCode() && !ptr) {
+ return false;
+ }
+
+ const SymbolicAddressSignature& callee =
+ f.isNoMemOrMem32() ? SASigWakeM32 : SASigWakeM64;
+
+ MDefinition* ret;
+ if (!f.emitInstanceCall2(bytecodeOffset, callee, ptr, count, &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitAtomicXchg(FunctionCompiler& f, ValType type,
+ Scalar::Type viewType) {
+ LinearMemoryAddress<MDefinition*> addr;
+ MDefinition* value;
+ if (!f.iter().readAtomicRMW(&addr, type, byteSize(viewType), &value)) {
+ return false;
+ }
+
+ MemoryAccessDesc access(viewType, addr.align, addr.offset, f.bytecodeOffset(),
+ Synchronization::Full());
+ MDefinition* ins = f.atomicExchangeHeap(addr.base, &access, type, value);
+ if (!f.inDeadCode() && !ins) {
+ return false;
+ }
+
+ f.iter().setResult(ins);
+ return true;
+}
+
+static bool EmitMemCopyCall(FunctionCompiler& f, MDefinition* dst,
+ MDefinition* src, MDefinition* len) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* memoryBase = f.memoryBase();
+ const SymbolicAddressSignature& callee =
+ (f.moduleEnv().usesSharedMemory()
+ ? (f.isMem32() ? SASigMemCopySharedM32 : SASigMemCopySharedM64)
+ : (f.isMem32() ? SASigMemCopyM32 : SASigMemCopyM64));
+
+ return f.emitInstanceCall4(bytecodeOffset, callee, dst, src, len, memoryBase);
+}
+
+static bool EmitMemCopyInline(FunctionCompiler& f, 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(Scalar::Simd128, 1, offset, f.bytecodeOffset());
+ auto* load = f.load(src, &access, ValType::V128);
+ if (!load || !loadedValues.append(load)) {
+ return false;
+ }
+
+ offset += sizeof(V128);
+ }
+#endif
+
+#ifdef JS_64BIT
+ for (uint32_t i = 0; i < numCopies8; i++) {
+ MemoryAccessDesc access(Scalar::Int64, 1, offset, f.bytecodeOffset());
+ auto* load = f.load(src, &access, ValType::I64);
+ if (!load || !loadedValues.append(load)) {
+ return false;
+ }
+
+ offset += sizeof(uint64_t);
+ }
+#endif
+
+ for (uint32_t i = 0; i < numCopies4; i++) {
+ MemoryAccessDesc access(Scalar::Uint32, 1, offset, f.bytecodeOffset());
+ auto* load = f.load(src, &access, ValType::I32);
+ if (!load || !loadedValues.append(load)) {
+ return false;
+ }
+
+ offset += sizeof(uint32_t);
+ }
+
+ if (numCopies2) {
+ MemoryAccessDesc access(Scalar::Uint16, 1, offset, f.bytecodeOffset());
+ auto* load = f.load(src, &access, ValType::I32);
+ if (!load || !loadedValues.append(load)) {
+ return false;
+ }
+
+ offset += sizeof(uint16_t);
+ }
+
+ if (numCopies1) {
+ MemoryAccessDesc access(Scalar::Uint8, 1, offset, f.bytecodeOffset());
+ auto* load = f.load(src, &access, ValType::I32);
+ if (!load || !loadedValues.append(load)) {
+ 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(Scalar::Uint8, 1, offset, f.bytecodeOffset());
+ auto* value = loadedValues.popCopy();
+ f.store(dst, &access, value);
+ }
+
+ if (numCopies2) {
+ offset -= sizeof(uint16_t);
+
+ MemoryAccessDesc access(Scalar::Uint16, 1, offset, f.bytecodeOffset());
+ auto* value = loadedValues.popCopy();
+ f.store(dst, &access, value);
+ }
+
+ for (uint32_t i = 0; i < numCopies4; i++) {
+ offset -= sizeof(uint32_t);
+
+ MemoryAccessDesc access(Scalar::Uint32, 1, offset, f.bytecodeOffset());
+ auto* value = loadedValues.popCopy();
+ f.store(dst, &access, value);
+ }
+
+#ifdef JS_64BIT
+ for (uint32_t i = 0; i < numCopies8; i++) {
+ offset -= sizeof(uint64_t);
+
+ MemoryAccessDesc access(Scalar::Int64, 1, offset, f.bytecodeOffset());
+ auto* value = loadedValues.popCopy();
+ f.store(dst, &access, value);
+ }
+#endif
+
+#ifdef ENABLE_WASM_SIMD
+ for (uint32_t i = 0; i < numCopies16; i++) {
+ offset -= sizeof(V128);
+
+ MemoryAccessDesc access(Scalar::Simd128, 1, offset, f.bytecodeOffset());
+ auto* value = loadedValues.popCopy();
+ f.store(dst, &access, value);
+ }
+#endif
+
+ return true;
+}
+
+static bool EmitMemCopy(FunctionCompiler& f) {
+ MDefinition *dst, *src, *len;
+ uint32_t dstMemIndex;
+ uint32_t srcMemIndex;
+ if (!f.iter().readMemOrTableCopy(true, &dstMemIndex, &dst, &srcMemIndex, &src,
+ &len)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ if (len->isConstant()) {
+ uint64_t length = f.isMem32() ? len->toConstant()->toInt32()
+ : len->toConstant()->toInt64();
+ static_assert(MaxInlineMemoryCopyLength <= UINT32_MAX);
+ if (length != 0 && length <= MaxInlineMemoryCopyLength) {
+ return EmitMemCopyInline(f, dst, src, uint32_t(length));
+ }
+ }
+
+ return EmitMemCopyCall(f, dst, src, len);
+}
+
+static bool EmitTableCopy(FunctionCompiler& f) {
+ MDefinition *dst, *src, *len;
+ uint32_t dstTableIndex;
+ uint32_t srcTableIndex;
+ if (!f.iter().readMemOrTableCopy(false, &dstTableIndex, &dst, &srcTableIndex,
+ &src, &len)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+ MDefinition* dti = f.constantI32(int32_t(dstTableIndex));
+ MDefinition* sti = f.constantI32(int32_t(srcTableIndex));
+
+ return f.emitInstanceCall5(bytecodeOffset, SASigTableCopy, dst, src, len, dti,
+ sti);
+}
+
+static bool EmitDataOrElemDrop(FunctionCompiler& f, bool isData) {
+ uint32_t segIndexVal = 0;
+ if (!f.iter().readDataOrElemDrop(isData, &segIndexVal)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* segIndex = f.constantI32(int32_t(segIndexVal));
+
+ const SymbolicAddressSignature& callee =
+ isData ? SASigDataDrop : SASigElemDrop;
+ return f.emitInstanceCall1(bytecodeOffset, callee, segIndex);
+}
+
+static bool EmitMemFillCall(FunctionCompiler& f, MDefinition* start,
+ MDefinition* val, MDefinition* len) {
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* memoryBase = f.memoryBase();
+
+ const SymbolicAddressSignature& callee =
+ (f.moduleEnv().usesSharedMemory()
+ ? (f.isMem32() ? SASigMemFillSharedM32 : SASigMemFillSharedM64)
+ : (f.isMem32() ? SASigMemFillM32 : SASigMemFillM64));
+ return f.emitInstanceCall4(bytecodeOffset, callee, start, val, len,
+ memoryBase);
+}
+
+static bool EmitMemFillInline(FunctionCompiler& f, 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 ? f.constantV128(V128(value)) : nullptr;
+#endif
+#ifdef JS_64BIT
+ MDefinition* val8 =
+ numCopies8 ? f.constantI64(int64_t(SplatByteToUInt<uint64_t>(value, 8)))
+ : nullptr;
+#endif
+ MDefinition* val4 =
+ numCopies4 ? f.constantI32(int32_t(SplatByteToUInt<uint32_t>(value, 4)))
+ : nullptr;
+ MDefinition* val2 =
+ numCopies2 ? f.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(Scalar::Uint8, 1, offset, f.bytecodeOffset());
+ f.store(start, &access, val);
+ }
+
+ if (numCopies2) {
+ offset -= sizeof(uint16_t);
+
+ MemoryAccessDesc access(Scalar::Uint16, 1, offset, f.bytecodeOffset());
+ f.store(start, &access, val2);
+ }
+
+ for (uint32_t i = 0; i < numCopies4; i++) {
+ offset -= sizeof(uint32_t);
+
+ MemoryAccessDesc access(Scalar::Uint32, 1, offset, f.bytecodeOffset());
+ f.store(start, &access, val4);
+ }
+
+#ifdef JS_64BIT
+ for (uint32_t i = 0; i < numCopies8; i++) {
+ offset -= sizeof(uint64_t);
+
+ MemoryAccessDesc access(Scalar::Int64, 1, offset, f.bytecodeOffset());
+ f.store(start, &access, val8);
+ }
+#endif
+
+#ifdef ENABLE_WASM_SIMD
+ for (uint32_t i = 0; i < numCopies16; i++) {
+ offset -= sizeof(V128);
+
+ MemoryAccessDesc access(Scalar::Simd128, 1, offset, f.bytecodeOffset());
+ f.store(start, &access, val16);
+ }
+#endif
+
+ return true;
+}
+
+static bool EmitMemFill(FunctionCompiler& f) {
+ MDefinition *start, *val, *len;
+ if (!f.iter().readMemFill(&start, &val, &len)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ if (len->isConstant() && val->isConstant()) {
+ uint64_t length = f.isMem32() ? len->toConstant()->toInt32()
+ : len->toConstant()->toInt64();
+ static_assert(MaxInlineMemoryFillLength <= UINT32_MAX);
+ if (length != 0 && length <= MaxInlineMemoryFillLength) {
+ return EmitMemFillInline(f, start, val, uint32_t(length));
+ }
+ }
+
+ return EmitMemFillCall(f, start, val, len);
+}
+
+static bool EmitMemOrTableInit(FunctionCompiler& f, bool isMem) {
+ uint32_t segIndexVal = 0, dstTableIndex = 0;
+ MDefinition *dstOff, *srcOff, *len;
+ if (!f.iter().readMemOrTableInit(isMem, &segIndexVal, &dstTableIndex, &dstOff,
+ &srcOff, &len)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* segIndex = f.constantI32(int32_t(segIndexVal));
+
+ if (isMem) {
+ const SymbolicAddressSignature& callee =
+ f.isMem32() ? SASigMemInitM32 : SASigMemInitM64;
+ return f.emitInstanceCall4(bytecodeOffset, callee, dstOff, srcOff, len,
+ segIndex);
+ }
+
+ MDefinition* dti = f.constantI32(int32_t(dstTableIndex));
+ return f.emitInstanceCall5(bytecodeOffset, SASigTableInit, dstOff, srcOff,
+ len, segIndex, dti);
+}
+
+// Note, table.{get,grow,set} on table(funcref) are currently rejected by the
+// verifier.
+
+static bool EmitTableFill(FunctionCompiler& f) {
+ uint32_t tableIndex;
+ MDefinition *start, *val, *len;
+ if (!f.iter().readTableFill(&tableIndex, &start, &val, &len)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* tableIndexArg = f.constantI32(int32_t(tableIndex));
+ if (!tableIndexArg) {
+ return false;
+ }
+
+ return f.emitInstanceCall4(bytecodeOffset, SASigTableFill, start, val, len,
+ tableIndexArg);
+}
+
+#if ENABLE_WASM_MEMORY_CONTROL
+static bool EmitMemDiscard(FunctionCompiler& f) {
+ MDefinition *start, *len;
+ if (!f.iter().readMemDiscard(&start, &len)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* memoryBase = f.memoryBase();
+
+ const SymbolicAddressSignature& callee =
+ (f.moduleEnv().usesSharedMemory()
+ ? (f.isMem32() ? SASigMemDiscardSharedM32 : SASigMemDiscardSharedM64)
+ : (f.isMem32() ? SASigMemDiscardM32 : SASigMemDiscardM64));
+ return f.emitInstanceCall3(bytecodeOffset, callee, start, len, memoryBase);
+}
+#endif
+
+static bool EmitTableGet(FunctionCompiler& f) {
+ uint32_t tableIndex;
+ MDefinition* index;
+ if (!f.iter().readTableGet(&tableIndex, &index)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ const TableDesc& table = f.moduleEnv().tables[tableIndex];
+ if (table.elemType.tableRepr() == TableRepr::Ref) {
+ MDefinition* ret = f.tableGetAnyRef(tableIndex, index);
+ if (!ret) {
+ return false;
+ }
+ f.iter().setResult(ret);
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* tableIndexArg = f.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 (!f.emitInstanceCall2(bytecodeOffset, SASigTableGet, index, tableIndexArg,
+ &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitTableGrow(FunctionCompiler& f) {
+ uint32_t tableIndex;
+ MDefinition* initValue;
+ MDefinition* delta;
+ if (!f.iter().readTableGrow(&tableIndex, &initValue, &delta)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* tableIndexArg = f.constantI32(int32_t(tableIndex));
+ if (!tableIndexArg) {
+ return false;
+ }
+
+ MDefinition* ret;
+ if (!f.emitInstanceCall3(bytecodeOffset, SASigTableGrow, initValue, delta,
+ tableIndexArg, &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitTableSet(FunctionCompiler& f) {
+ uint32_t tableIndex;
+ MDefinition* index;
+ MDefinition* value;
+ if (!f.iter().readTableSet(&tableIndex, &index, &value)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ const TableDesc& table = f.moduleEnv().tables[tableIndex];
+ if (table.elemType.tableRepr() == TableRepr::Ref) {
+ return f.tableSetAnyRef(tableIndex, index, value, bytecodeOffset);
+ }
+
+ MDefinition* tableIndexArg = f.constantI32(int32_t(tableIndex));
+ if (!tableIndexArg) {
+ return false;
+ }
+
+ return f.emitInstanceCall3(bytecodeOffset, SASigTableSet, index, value,
+ tableIndexArg);
+}
+
+static bool EmitTableSize(FunctionCompiler& f) {
+ uint32_t tableIndex;
+ if (!f.iter().readTableSize(&tableIndex)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ MDefinition* length = f.loadTableLength(tableIndex);
+ if (!length) {
+ return false;
+ }
+
+ f.iter().setResult(length);
+ return true;
+}
+
+static bool EmitRefFunc(FunctionCompiler& f) {
+ uint32_t funcIndex;
+ if (!f.iter().readRefFunc(&funcIndex)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+
+ MDefinition* funcIndexArg = f.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 (!f.emitInstanceCall1(bytecodeOffset, SASigRefFunc, funcIndexArg, &ret)) {
+ return false;
+ }
+
+ f.iter().setResult(ret);
+ return true;
+}
+
+static bool EmitRefNull(FunctionCompiler& f) {
+ RefType type;
+ if (!f.iter().readRefNull(&type)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ MDefinition* nullVal = f.constantNullRef();
+ if (!nullVal) {
+ return false;
+ }
+ f.iter().setResult(nullVal);
+ return true;
+}
+
+static bool EmitRefIsNull(FunctionCompiler& f) {
+ MDefinition* input;
+ if (!f.iter().readRefIsNull(&input)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ MDefinition* nullVal = f.constantNullRef();
+ if (!nullVal) {
+ return false;
+ }
+ f.iter().setResult(
+ f.compare(input, nullVal, JSOp::Eq, MCompare::Compare_RefOrNull));
+ return true;
+}
+
+#ifdef ENABLE_WASM_SIMD
+static bool EmitConstSimd128(FunctionCompiler& f) {
+ V128 v128;
+ if (!f.iter().readV128Const(&v128)) {
+ return false;
+ }
+
+ f.iter().setResult(f.constantV128(v128));
+ return true;
+}
+
+static bool EmitBinarySimd128(FunctionCompiler& f, bool commutative,
+ SimdOp op) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readBinary(ValType::V128, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.binarySimd128(lhs, rhs, commutative, op));
+ return true;
+}
+
+static bool EmitTernarySimd128(FunctionCompiler& f, wasm::SimdOp op) {
+ MDefinition* v0;
+ MDefinition* v1;
+ MDefinition* v2;
+ if (!f.iter().readTernary(ValType::V128, &v0, &v1, &v2)) {
+ return false;
+ }
+
+ f.iter().setResult(f.ternarySimd128(v0, v1, v2, op));
+ return true;
+}
+
+static bool EmitShiftSimd128(FunctionCompiler& f, SimdOp op) {
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readVectorShift(&lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.shiftSimd128(lhs, rhs, op));
+ return true;
+}
+
+static bool EmitSplatSimd128(FunctionCompiler& f, ValType inType, SimdOp op) {
+ MDefinition* src;
+ if (!f.iter().readConversion(inType, ValType::V128, &src)) {
+ return false;
+ }
+
+ f.iter().setResult(f.scalarToSimd128(src, op));
+ return true;
+}
+
+static bool EmitUnarySimd128(FunctionCompiler& f, SimdOp op) {
+ MDefinition* src;
+ if (!f.iter().readUnary(ValType::V128, &src)) {
+ return false;
+ }
+
+ f.iter().setResult(f.unarySimd128(src, op));
+ return true;
+}
+
+static bool EmitReduceSimd128(FunctionCompiler& f, SimdOp op) {
+ MDefinition* src;
+ if (!f.iter().readConversion(ValType::V128, ValType::I32, &src)) {
+ return false;
+ }
+
+ f.iter().setResult(f.reduceSimd128(src, op, ValType::I32));
+ return true;
+}
+
+static bool EmitExtractLaneSimd128(FunctionCompiler& f, ValType outType,
+ uint32_t laneLimit, SimdOp op) {
+ uint32_t laneIndex;
+ MDefinition* src;
+ if (!f.iter().readExtractLane(outType, laneLimit, &laneIndex, &src)) {
+ return false;
+ }
+
+ f.iter().setResult(f.reduceSimd128(src, op, outType, laneIndex));
+ return true;
+}
+
+static bool EmitReplaceLaneSimd128(FunctionCompiler& f, ValType laneType,
+ uint32_t laneLimit, SimdOp op) {
+ uint32_t laneIndex;
+ MDefinition* lhs;
+ MDefinition* rhs;
+ if (!f.iter().readReplaceLane(laneType, laneLimit, &laneIndex, &lhs, &rhs)) {
+ return false;
+ }
+
+ f.iter().setResult(f.replaceLaneSimd128(lhs, rhs, laneIndex, op));
+ return true;
+}
+
+static bool EmitShuffleSimd128(FunctionCompiler& f) {
+ MDefinition* v1;
+ MDefinition* v2;
+ V128 control;
+ if (!f.iter().readVectorShuffle(&v1, &v2, &control)) {
+ return false;
+ }
+
+ f.iter().setResult(f.shuffleSimd128(v1, v2, control));
+ return true;
+}
+
+static bool EmitLoadSplatSimd128(FunctionCompiler& f, Scalar::Type viewType,
+ wasm::SimdOp splatOp) {
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readLoadSplat(Scalar::byteSize(viewType), &addr)) {
+ return false;
+ }
+
+ f.iter().setResult(f.loadSplatSimd128(viewType, addr, splatOp));
+ return true;
+}
+
+static bool EmitLoadExtendSimd128(FunctionCompiler& f, wasm::SimdOp op) {
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readLoadExtend(&addr)) {
+ return false;
+ }
+
+ f.iter().setResult(f.loadExtendSimd128(addr, op));
+ return true;
+}
+
+static bool EmitLoadZeroSimd128(FunctionCompiler& f, Scalar::Type viewType,
+ size_t numBytes) {
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readLoadSplat(numBytes, &addr)) {
+ return false;
+ }
+
+ f.iter().setResult(f.loadZeroSimd128(viewType, numBytes, addr));
+ return true;
+}
+
+static bool EmitLoadLaneSimd128(FunctionCompiler& f, uint32_t laneSize) {
+ uint32_t laneIndex;
+ MDefinition* src;
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readLoadLane(laneSize, &addr, &laneIndex, &src)) {
+ return false;
+ }
+
+ f.iter().setResult(f.loadLaneSimd128(laneSize, addr, laneIndex, src));
+ return true;
+}
+
+static bool EmitStoreLaneSimd128(FunctionCompiler& f, uint32_t laneSize) {
+ uint32_t laneIndex;
+ MDefinition* src;
+ LinearMemoryAddress<MDefinition*> addr;
+ if (!f.iter().readStoreLane(laneSize, &addr, &laneIndex, &src)) {
+ return false;
+ }
+
+ f.storeLaneSimd128(laneSize, addr, laneIndex, src);
+ return true;
+}
+
+#endif // ENABLE_WASM_SIMD
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+static bool EmitRefAsNonNull(FunctionCompiler& f) {
+ MDefinition* value;
+ if (!f.iter().readRefAsNonNull(&value)) {
+ return false;
+ }
+
+ return f.refAsNonNull(value);
+}
+
+static bool EmitBrOnNull(FunctionCompiler& f) {
+ uint32_t relativeDepth;
+ ResultType type;
+ DefVector values;
+ MDefinition* condition;
+ if (!f.iter().readBrOnNull(&relativeDepth, &type, &values, &condition)) {
+ return false;
+ }
+
+ return f.brOnNull(relativeDepth, values, type, condition);
+}
+
+static bool EmitBrOnNonNull(FunctionCompiler& f) {
+ uint32_t relativeDepth;
+ ResultType type;
+ DefVector values;
+ MDefinition* condition;
+ if (!f.iter().readBrOnNonNull(&relativeDepth, &type, &values, &condition)) {
+ return false;
+ }
+
+ return f.brOnNonNull(relativeDepth, values, type, condition);
+}
+
+static bool EmitCallRef(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ const FuncType* funcType;
+ MDefinition* callee;
+ DefVector args;
+
+ if (!f.iter().readCallRef(&funcType, &callee, &args)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ CallCompileState call;
+ if (!EmitCallArgs(f, *funcType, args, &call)) {
+ return false;
+ }
+
+ DefVector results;
+ if (!f.callRef(*funcType, callee, lineOrBytecode, call, &results)) {
+ return false;
+ }
+
+ f.iter().setResults(results.length(), results);
+ return true;
+}
+
+#endif // ENABLE_WASM_FUNCTION_REFERENCES
+
+#ifdef ENABLE_WASM_GC
+
+static bool EmitStructNew(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ DefVector args;
+ if (!f.iter().readStructNew(&typeIndex, &args)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ const StructType& structType = (*f.moduleEnv().types)[typeIndex].structType();
+ MOZ_ASSERT(args.length() == structType.fields_.length());
+
+ // Allocate a default initialized struct. This requires the type definition
+ // for the struct.
+ MDefinition* typeDefData = f.loadTypeDefInstanceData(typeIndex);
+ if (!typeDefData) {
+ return false;
+ }
+
+ // Create call: structObject = Instance::structNewUninit(typeDefData)
+ MDefinition* structObject;
+ if (!f.emitInstanceCall1(lineOrBytecode, SASigStructNewUninit, typeDefData,
+ &structObject)) {
+ return false;
+ }
+
+ // And fill in the fields.
+ for (uint32_t fieldIndex = 0; fieldIndex < structType.fields_.length();
+ fieldIndex++) {
+ if (!f.mirGen().ensureBallast()) {
+ return false;
+ }
+ const StructField& field = structType.fields_[fieldIndex];
+ if (!f.writeValueToStructField(lineOrBytecode, field, structObject,
+ args[fieldIndex],
+ WasmPreBarrierKind::None)) {
+ return false;
+ }
+ }
+
+ f.iter().setResult(structObject);
+ return true;
+}
+
+static bool EmitStructNewDefault(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ if (!f.iter().readStructNewDefault(&typeIndex)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Allocate a default initialized struct. This requires the type definition
+ // for the struct.
+ MDefinition* typeDefData = f.loadTypeDefInstanceData(typeIndex);
+ if (!typeDefData) {
+ return false;
+ }
+
+ // Create call: structObject = Instance::structNew(typeDefData)
+ MDefinition* structObject;
+ if (!f.emitInstanceCall1(lineOrBytecode, SASigStructNew, typeDefData,
+ &structObject)) {
+ return false;
+ }
+
+ f.iter().setResult(structObject);
+ return true;
+}
+
+static bool EmitStructSet(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ uint32_t fieldIndex;
+ MDefinition* structObject;
+ MDefinition* value;
+ if (!f.iter().readStructSet(&typeIndex, &fieldIndex, &structObject, &value)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Check for null is done at writeValueToStructField.
+
+ // And fill in the field.
+ const StructType& structType = (*f.moduleEnv().types)[typeIndex].structType();
+ const StructField& field = structType.fields_[fieldIndex];
+ return f.writeValueToStructField(lineOrBytecode, field, structObject, value,
+ WasmPreBarrierKind::Normal);
+}
+
+static bool EmitStructGet(FunctionCompiler& f, FieldWideningOp wideningOp) {
+ uint32_t typeIndex;
+ uint32_t fieldIndex;
+ MDefinition* structObject;
+ if (!f.iter().readStructGet(&typeIndex, &fieldIndex, wideningOp,
+ &structObject)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Check for null is done at readValueFromStructField.
+
+ // And fetch the data.
+ const StructType& structType = (*f.moduleEnv().types)[typeIndex].structType();
+ const StructField& field = structType.fields_[fieldIndex];
+ MDefinition* load =
+ f.readValueFromStructField(field, wideningOp, structObject);
+ if (!load) {
+ return false;
+ }
+
+ f.iter().setResult(load);
+ return true;
+}
+
+static bool EmitArrayNew(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ MDefinition* numElements;
+ MDefinition* fillValue;
+ if (!f.iter().readArrayNew(&typeIndex, &numElements, &fillValue)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // If the requested size exceeds MaxArrayPayloadBytes, the MIR generated by
+ // this helper will trap.
+ MDefinition* arrayObject = f.createArrayNewCallAndLoop(
+ lineOrBytecode, typeIndex, numElements, fillValue);
+ if (!arrayObject) {
+ return false;
+ }
+
+ f.iter().setResult(arrayObject);
+ return true;
+}
+
+static bool EmitArrayNewDefault(FunctionCompiler& f) {
+ // This is almost identical to EmitArrayNew, except we skip the
+ // initialisation loop.
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ MDefinition* numElements;
+ if (!f.iter().readArrayNewDefault(&typeIndex, &numElements)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Create the array object, default-initialized.
+ MDefinition* arrayObject = f.createDefaultInitializedArrayObject(
+ lineOrBytecode, typeIndex, numElements);
+ if (!arrayObject) {
+ return false;
+ }
+
+ f.iter().setResult(arrayObject);
+ return true;
+}
+
+static bool EmitArrayNewFixed(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex, numElements;
+ DefVector values;
+
+ if (!f.iter().readArrayNewFixed(&typeIndex, &numElements, &values)) {
+ return false;
+ }
+ MOZ_ASSERT(values.length() == numElements);
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ MDefinition* numElementsDef = f.constantI32(int32_t(numElements));
+ if (!numElementsDef) {
+ return false;
+ }
+
+ // Create the array object, default-initialized.
+ MDefinition* arrayObject = f.createDefaultInitializedArrayObject(
+ lineOrBytecode, typeIndex, numElementsDef);
+ if (!arrayObject) {
+ return false;
+ }
+
+ // Make `base` point at the first byte of the (OOL) data area.
+ MDefinition* base = f.getWasmArrayObjectData(arrayObject);
+ if (!base) {
+ return false;
+ }
+
+ // Write each element in turn.
+ const ArrayType& arrayType = (*f.moduleEnv().types)[typeIndex].arrayType();
+ FieldType elemFieldType = arrayType.elementType_;
+ uint32_t elemSize = elemFieldType.size();
+
+ // 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 (!f.mirGen().ensureBallast()) {
+ return false;
+ }
+ // `i * elemSize` is made safe by the assertions above.
+ if (!f.writeGcValueAtBasePlusOffset(
+ lineOrBytecode, elemFieldType, arrayObject,
+ AliasSet::WasmArrayDataArea, values[numElements - 1 - i], base,
+ i * elemSize, false, WasmPreBarrierKind::None)) {
+ return false;
+ }
+ }
+
+ f.iter().setResult(arrayObject);
+ return true;
+}
+
+static bool EmitArrayNewData(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex, segIndex;
+ MDefinition* segByteOffset;
+ MDefinition* numElements;
+ if (!f.iter().readArrayNewData(&typeIndex, &segIndex, &segByteOffset,
+ &numElements)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Get the type definition data for the array as a whole.
+ MDefinition* typeDefData = f.loadTypeDefInstanceData(typeIndex);
+ if (!typeDefData) {
+ return false;
+ }
+
+ // Other values we need to pass to the instance call:
+ MDefinition* segIndexM = f.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 (!f.emitInstanceCall4(lineOrBytecode, SASigArrayNewData, segByteOffset,
+ numElements, typeDefData, segIndexM, &arrayObject)) {
+ return false;
+ }
+
+ f.iter().setResult(arrayObject);
+ return true;
+}
+
+static bool EmitArrayNewElem(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex, segIndex;
+ MDefinition* segElemIndex;
+ MDefinition* numElements;
+ if (!f.iter().readArrayNewElem(&typeIndex, &segIndex, &segElemIndex,
+ &numElements)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Get the type definition for the array as a whole.
+ // Get the type definition data for the array as a whole.
+ MDefinition* typeDefData = f.loadTypeDefInstanceData(typeIndex);
+ if (!typeDefData) {
+ return false;
+ }
+
+ // Other values we need to pass to the instance call:
+ MDefinition* segIndexM = f.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 (!f.emitInstanceCall4(lineOrBytecode, SASigArrayNewElem, segElemIndex,
+ numElements, typeDefData, segIndexM, &arrayObject)) {
+ return false;
+ }
+
+ f.iter().setResult(arrayObject);
+ return true;
+}
+
+static bool EmitArraySet(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ uint32_t typeIndex;
+ MDefinition* value;
+ MDefinition* index;
+ MDefinition* arrayObject;
+ if (!f.iter().readArraySet(&typeIndex, &value, &index, &arrayObject)) {
+ return false;
+ }
+
+ if (f.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 = f.setupForArrayAccess(arrayObject, index);
+ if (!base) {
+ return false;
+ }
+
+ // And do the store.
+ const ArrayType& arrayType = (*f.moduleEnv().types)[typeIndex].arrayType();
+ FieldType elemFieldType = arrayType.elementType_;
+ uint32_t elemSize = elemFieldType.size();
+ MOZ_ASSERT(elemSize >= 1 && elemSize <= 16);
+
+ return f.writeGcValueAtBasePlusScaledIndex(
+ lineOrBytecode, elemFieldType, arrayObject, AliasSet::WasmArrayDataArea,
+ value, base, elemSize, index, WasmPreBarrierKind::Normal);
+}
+
+static bool EmitArrayGet(FunctionCompiler& f, FieldWideningOp wideningOp) {
+ uint32_t typeIndex;
+ MDefinition* index;
+ MDefinition* arrayObject;
+ if (!f.iter().readArrayGet(&typeIndex, wideningOp, &index, &arrayObject)) {
+ return false;
+ }
+
+ if (f.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 = f.setupForArrayAccess(arrayObject, index);
+ if (!base) {
+ return false;
+ }
+
+ // And do the load.
+ const ArrayType& arrayType = (*f.moduleEnv().types)[typeIndex].arrayType();
+ FieldType elemFieldType = arrayType.elementType_;
+ uint32_t elemSize = elemFieldType.size();
+ MOZ_ASSERT(elemSize >= 1 && elemSize <= 16);
+
+ MDefinition* load = f.readGcValueAtBasePlusScaledIndex(
+ elemFieldType, wideningOp, arrayObject, AliasSet::WasmArrayDataArea, base,
+ elemSize, index);
+ if (!load) {
+ return false;
+ }
+
+ f.iter().setResult(load);
+ return true;
+}
+
+static bool EmitArrayLen(FunctionCompiler& f, bool decodeIgnoredTypeIndex) {
+ MDefinition* arrayObject;
+ if (!f.iter().readArrayLen(decodeIgnoredTypeIndex, &arrayObject)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ // Check for null is done at getWasmArrayObjectNumElements.
+
+ // Get the size value for the array
+ MDefinition* numElements = f.getWasmArrayObjectNumElements(arrayObject);
+ if (!numElements) {
+ return false;
+ }
+
+ f.iter().setResult(numElements);
+ return true;
+}
+
+static bool EmitArrayCopy(FunctionCompiler& f) {
+ uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+ int32_t elemSize;
+ bool elemsAreRefTyped;
+ MDefinition* dstArrayObject;
+ MDefinition* dstArrayIndex;
+ MDefinition* srcArrayObject;
+ MDefinition* srcArrayIndex;
+ MDefinition* numElements;
+ if (!f.iter().readArrayCopy(&elemSize, &elemsAreRefTyped, &dstArrayObject,
+ &dstArrayIndex, &srcArrayObject, &srcArrayIndex,
+ &numElements)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ MOZ_ASSERT_IF(elemsAreRefTyped,
+ size_t(elemSize) == MIRTypeToSize(TargetWordMIRType()));
+ MOZ_ASSERT_IF(!elemsAreRefTyped, elemSize == 1 || elemSize == 2 ||
+ elemSize == 4 || elemSize == 8 ||
+ elemSize == 16);
+
+ // A negative element size is used to inform Instance::arrayCopy that the
+ // values are reftyped. This avoids having to pass it an extra boolean
+ // argument.
+ MDefinition* elemSizeDef =
+ f.constantI32(elemsAreRefTyped ? -elemSize : elemSize);
+ if (!elemSizeDef) {
+ return false;
+ }
+
+ // Create call:
+ // Instance::arrayCopy(dstArrayObject:word, dstArrayIndex:u32,
+ // srcArrayObject:word, srcArrayIndex:u32,
+ // numElements:u32,
+ // (elemsAreRefTyped ? -elemSize : elemSize):u32))
+ return f.emitInstanceCall6(lineOrBytecode, SASigArrayCopy, dstArrayObject,
+ dstArrayIndex, srcArrayObject, srcArrayIndex,
+ numElements, elemSizeDef);
+}
+
+static bool EmitRefTestV5(FunctionCompiler& f) {
+ MDefinition* ref;
+ RefType sourceType;
+ uint32_t typeIndex;
+ if (!f.iter().readRefTestV5(&sourceType, &typeIndex, &ref)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ const TypeDef& typeDef = f.moduleEnv().types->type(typeIndex);
+ RefType destType = RefType::fromTypeDef(&typeDef, false);
+ MDefinition* success = f.refTest(ref, sourceType, destType);
+ if (!success) {
+ return false;
+ }
+
+ f.iter().setResult(success);
+ return true;
+}
+
+static bool EmitRefCastV5(FunctionCompiler& f) {
+ MDefinition* ref;
+ RefType sourceType;
+ uint32_t typeIndex;
+ if (!f.iter().readRefCastV5(&sourceType, &typeIndex, &ref)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ const TypeDef& typeDef = f.moduleEnv().types->type(typeIndex);
+ RefType destType = RefType::fromTypeDef(&typeDef, /*nullable=*/true);
+ if (!f.refCast(ref, sourceType, destType)) {
+ return false;
+ }
+
+ f.iter().setResult(ref);
+ return true;
+}
+
+static bool EmitRefTest(FunctionCompiler& f, bool nullable) {
+ MDefinition* ref;
+ RefType sourceType;
+ RefType destType;
+ if (!f.iter().readRefTest(nullable, &sourceType, &destType, &ref)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ MDefinition* success = f.refTest(ref, sourceType, destType);
+ if (!success) {
+ return false;
+ }
+
+ f.iter().setResult(success);
+ return true;
+}
+
+static bool EmitRefCast(FunctionCompiler& f, bool nullable) {
+ MDefinition* ref;
+ RefType sourceType;
+ RefType destType;
+ if (!f.iter().readRefCast(nullable, &sourceType, &destType, &ref)) {
+ return false;
+ }
+
+ if (f.inDeadCode()) {
+ return true;
+ }
+
+ if (!f.refCast(ref, sourceType, destType)) {
+ return false;
+ }
+
+ f.iter().setResult(ref);
+ return true;
+}
+
+static bool EmitBrOnCast(FunctionCompiler& f) {
+ bool onSuccess;
+ uint32_t labelRelativeDepth;
+ RefType sourceType;
+ RefType destType;
+ ResultType labelType;
+ DefVector values;
+ if (!f.iter().readBrOnCast(&onSuccess, &labelRelativeDepth, &sourceType,
+ &destType, &labelType, &values)) {
+ return false;
+ }
+
+ return f.brOnCastCommon(onSuccess, labelRelativeDepth, sourceType, destType,
+ labelType, values);
+}
+
+static bool EmitBrOnCastCommonV5(FunctionCompiler& f, bool onSuccess) {
+ uint32_t labelRelativeDepth;
+ RefType sourceType;
+ uint32_t castTypeIndex;
+ ResultType labelType;
+ DefVector values;
+ if (onSuccess
+ ? !f.iter().readBrOnCastV5(&labelRelativeDepth, &sourceType,
+ &castTypeIndex, &labelType, &values)
+ : !f.iter().readBrOnCastFailV5(&labelRelativeDepth, &sourceType,
+ &castTypeIndex, &labelType, &values)) {
+ return false;
+ }
+
+ const TypeDef& typeDef = f.moduleEnv().types->type(castTypeIndex);
+ RefType type = RefType::fromTypeDef(&typeDef, false);
+ return f.brOnCastCommon(onSuccess, labelRelativeDepth, sourceType, type,
+ labelType, values);
+}
+
+static bool EmitBrOnCastHeapV5(FunctionCompiler& f, bool onSuccess,
+ bool nullable) {
+ uint32_t labelRelativeDepth;
+ RefType sourceType;
+ RefType destType;
+ ResultType labelType;
+ DefVector values;
+ if (onSuccess ? !f.iter().readBrOnCastHeapV5(nullable, &labelRelativeDepth,
+ &sourceType, &destType,
+ &labelType, &values)
+ : !f.iter().readBrOnCastFailHeapV5(
+ nullable, &labelRelativeDepth, &sourceType, &destType,
+ &labelType, &values)) {
+ return false;
+ }
+
+ return f.brOnCastCommon(onSuccess, labelRelativeDepth, sourceType, destType,
+ labelType, values);
+}
+
+static bool EmitRefAsStructV5(FunctionCompiler& f) {
+ MDefinition* value;
+ if (!f.iter().readConversion(ValType(RefType::any()),
+ ValType(RefType::struct_().asNonNullable()),
+ &value)) {
+ return false;
+ }
+ f.iter().setResult(value);
+ return true;
+}
+
+static bool EmitBrOnNonStructV5(FunctionCompiler& f) {
+ uint32_t labelRelativeDepth;
+ ResultType labelType;
+ DefVector values;
+ if (!f.iter().readBrOnNonStructV5(&labelRelativeDepth, &labelType, &values)) {
+ return false;
+ }
+ return f.brOnNonStruct(values);
+}
+
+static bool EmitExternInternalize(FunctionCompiler& f) {
+ // extern.internalize is a no-op because anyref and extern share the same
+ // representation
+ MDefinition* ref;
+ if (!f.iter().readRefConversion(RefType::extern_(), RefType::any(), &ref)) {
+ return false;
+ }
+
+ f.iter().setResult(ref);
+ return true;
+}
+
+static bool EmitExternExternalize(FunctionCompiler& f) {
+ // extern.externalize is a no-op because anyref and extern share the same
+ // representation
+ MDefinition* ref;
+ if (!f.iter().readRefConversion(RefType::any(), RefType::extern_(), &ref)) {
+ return false;
+ }
+
+ f.iter().setResult(ref);
+ return true;
+}
+
+#endif // ENABLE_WASM_GC
+
+static bool EmitIntrinsic(FunctionCompiler& f) {
+ // 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
+ // `intrinsic->params` into a fixed-sized (16 element?) array, add
+ // `memoryBase`, and make the call.
+ const Intrinsic* intrinsic;
+
+ DefVector params;
+ if (!f.iter().readIntrinsic(&intrinsic, &params)) {
+ return false;
+ }
+
+ uint32_t bytecodeOffset = f.readBytecodeOffset();
+ const SymbolicAddressSignature& callee = intrinsic->signature;
+
+ CallCompileState args;
+ if (!f.passInstance(callee.argTypes[0], &args)) {
+ return false;
+ }
+
+ if (!f.passArgs(params, intrinsic->params, &args)) {
+ return false;
+ }
+
+ MDefinition* memoryBase = f.memoryBase();
+ if (!f.passArg(memoryBase, MIRType::Pointer, &args)) {
+ return false;
+ }
+
+ if (!f.finishCall(&args)) {
+ return false;
+ }
+
+ return f.builtinInstanceMethodCall(callee, bytecodeOffset, args);
+}
+
+static bool EmitBodyExprs(FunctionCompiler& f) {
+ if (!f.iter().startFunction(f.funcIndex(), f.locals())) {
+ return false;
+ }
+
+#define CHECK(c) \
+ if (!(c)) return false; \
+ break
+
+ while (true) {
+ if (!f.mirGen().ensureBallast()) {
+ return false;
+ }
+
+ OpBytes op;
+ if (!f.iter().readOp(&op)) {
+ return false;
+ }
+
+ switch (op.b0) {
+ case uint16_t(Op::End):
+ if (!EmitEnd(f)) {
+ return false;
+ }
+ if (f.iter().controlStackEmpty()) {
+ return true;
+ }
+ break;
+
+ // Control opcodes
+ case uint16_t(Op::Unreachable):
+ CHECK(EmitUnreachable(f));
+ case uint16_t(Op::Nop):
+ CHECK(f.iter().readNop());
+ case uint16_t(Op::Block):
+ CHECK(EmitBlock(f));
+ case uint16_t(Op::Loop):
+ CHECK(EmitLoop(f));
+ case uint16_t(Op::If):
+ CHECK(EmitIf(f));
+ case uint16_t(Op::Else):
+ CHECK(EmitElse(f));
+ case uint16_t(Op::Try):
+ if (!f.moduleEnv().exceptionsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitTry(f));
+ case uint16_t(Op::Catch):
+ if (!f.moduleEnv().exceptionsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitCatch(f));
+ case uint16_t(Op::CatchAll):
+ if (!f.moduleEnv().exceptionsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitCatchAll(f));
+ case uint16_t(Op::Delegate):
+ if (!f.moduleEnv().exceptionsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ if (!EmitDelegate(f)) {
+ return false;
+ }
+ break;
+ case uint16_t(Op::Throw):
+ if (!f.moduleEnv().exceptionsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitThrow(f));
+ case uint16_t(Op::Rethrow):
+ if (!f.moduleEnv().exceptionsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitRethrow(f));
+ case uint16_t(Op::Br):
+ CHECK(EmitBr(f));
+ case uint16_t(Op::BrIf):
+ CHECK(EmitBrIf(f));
+ case uint16_t(Op::BrTable):
+ CHECK(EmitBrTable(f));
+ case uint16_t(Op::Return):
+ CHECK(EmitReturn(f));
+
+ // Calls
+ case uint16_t(Op::Call):
+ CHECK(EmitCall(f, /* asmJSFuncDef = */ false));
+ case uint16_t(Op::CallIndirect):
+ CHECK(EmitCallIndirect(f, /* oldStyle = */ false));
+
+ // Parametric operators
+ case uint16_t(Op::Drop):
+ CHECK(f.iter().readDrop());
+ case uint16_t(Op::SelectNumeric):
+ CHECK(EmitSelect(f, /*typed*/ false));
+ case uint16_t(Op::SelectTyped):
+ CHECK(EmitSelect(f, /*typed*/ true));
+
+ // Locals and globals
+ case uint16_t(Op::LocalGet):
+ CHECK(EmitGetLocal(f));
+ case uint16_t(Op::LocalSet):
+ CHECK(EmitSetLocal(f));
+ case uint16_t(Op::LocalTee):
+ CHECK(EmitTeeLocal(f));
+ case uint16_t(Op::GlobalGet):
+ CHECK(EmitGetGlobal(f));
+ case uint16_t(Op::GlobalSet):
+ CHECK(EmitSetGlobal(f));
+ case uint16_t(Op::TableGet):
+ CHECK(EmitTableGet(f));
+ case uint16_t(Op::TableSet):
+ CHECK(EmitTableSet(f));
+
+ // Memory-related operators
+ case uint16_t(Op::I32Load):
+ CHECK(EmitLoad(f, ValType::I32, Scalar::Int32));
+ case uint16_t(Op::I64Load):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Int64));
+ case uint16_t(Op::F32Load):
+ CHECK(EmitLoad(f, ValType::F32, Scalar::Float32));
+ case uint16_t(Op::F64Load):
+ CHECK(EmitLoad(f, ValType::F64, Scalar::Float64));
+ case uint16_t(Op::I32Load8S):
+ CHECK(EmitLoad(f, ValType::I32, Scalar::Int8));
+ case uint16_t(Op::I32Load8U):
+ CHECK(EmitLoad(f, ValType::I32, Scalar::Uint8));
+ case uint16_t(Op::I32Load16S):
+ CHECK(EmitLoad(f, ValType::I32, Scalar::Int16));
+ case uint16_t(Op::I32Load16U):
+ CHECK(EmitLoad(f, ValType::I32, Scalar::Uint16));
+ case uint16_t(Op::I64Load8S):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Int8));
+ case uint16_t(Op::I64Load8U):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Uint8));
+ case uint16_t(Op::I64Load16S):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Int16));
+ case uint16_t(Op::I64Load16U):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Uint16));
+ case uint16_t(Op::I64Load32S):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Int32));
+ case uint16_t(Op::I64Load32U):
+ CHECK(EmitLoad(f, ValType::I64, Scalar::Uint32));
+ case uint16_t(Op::I32Store):
+ CHECK(EmitStore(f, ValType::I32, Scalar::Int32));
+ case uint16_t(Op::I64Store):
+ CHECK(EmitStore(f, ValType::I64, Scalar::Int64));
+ case uint16_t(Op::F32Store):
+ CHECK(EmitStore(f, ValType::F32, Scalar::Float32));
+ case uint16_t(Op::F64Store):
+ CHECK(EmitStore(f, ValType::F64, Scalar::Float64));
+ case uint16_t(Op::I32Store8):
+ CHECK(EmitStore(f, ValType::I32, Scalar::Int8));
+ case uint16_t(Op::I32Store16):
+ CHECK(EmitStore(f, ValType::I32, Scalar::Int16));
+ case uint16_t(Op::I64Store8):
+ CHECK(EmitStore(f, ValType::I64, Scalar::Int8));
+ case uint16_t(Op::I64Store16):
+ CHECK(EmitStore(f, ValType::I64, Scalar::Int16));
+ case uint16_t(Op::I64Store32):
+ CHECK(EmitStore(f, ValType::I64, Scalar::Int32));
+ case uint16_t(Op::MemorySize):
+ CHECK(EmitMemorySize(f));
+ case uint16_t(Op::MemoryGrow):
+ CHECK(EmitMemoryGrow(f));
+
+ // Constants
+ case uint16_t(Op::I32Const):
+ CHECK(EmitI32Const(f));
+ case uint16_t(Op::I64Const):
+ CHECK(EmitI64Const(f));
+ case uint16_t(Op::F32Const):
+ CHECK(EmitF32Const(f));
+ case uint16_t(Op::F64Const):
+ CHECK(EmitF64Const(f));
+
+ // Comparison operators
+ case uint16_t(Op::I32Eqz):
+ CHECK(EmitConversion<MNot>(f, ValType::I32, ValType::I32));
+ case uint16_t(Op::I32Eq):
+ CHECK(
+ EmitComparison(f, ValType::I32, JSOp::Eq, MCompare::Compare_Int32));
+ case uint16_t(Op::I32Ne):
+ CHECK(
+ EmitComparison(f, ValType::I32, JSOp::Ne, MCompare::Compare_Int32));
+ case uint16_t(Op::I32LtS):
+ CHECK(
+ EmitComparison(f, ValType::I32, JSOp::Lt, MCompare::Compare_Int32));
+ case uint16_t(Op::I32LtU):
+ CHECK(EmitComparison(f, ValType::I32, JSOp::Lt,
+ MCompare::Compare_UInt32));
+ case uint16_t(Op::I32GtS):
+ CHECK(
+ EmitComparison(f, ValType::I32, JSOp::Gt, MCompare::Compare_Int32));
+ case uint16_t(Op::I32GtU):
+ CHECK(EmitComparison(f, ValType::I32, JSOp::Gt,
+ MCompare::Compare_UInt32));
+ case uint16_t(Op::I32LeS):
+ CHECK(
+ EmitComparison(f, ValType::I32, JSOp::Le, MCompare::Compare_Int32));
+ case uint16_t(Op::I32LeU):
+ CHECK(EmitComparison(f, ValType::I32, JSOp::Le,
+ MCompare::Compare_UInt32));
+ case uint16_t(Op::I32GeS):
+ CHECK(
+ EmitComparison(f, ValType::I32, JSOp::Ge, MCompare::Compare_Int32));
+ case uint16_t(Op::I32GeU):
+ CHECK(EmitComparison(f, ValType::I32, JSOp::Ge,
+ MCompare::Compare_UInt32));
+ case uint16_t(Op::I64Eqz):
+ CHECK(EmitConversion<MNot>(f, ValType::I64, ValType::I32));
+ case uint16_t(Op::I64Eq):
+ CHECK(
+ EmitComparison(f, ValType::I64, JSOp::Eq, MCompare::Compare_Int64));
+ case uint16_t(Op::I64Ne):
+ CHECK(
+ EmitComparison(f, ValType::I64, JSOp::Ne, MCompare::Compare_Int64));
+ case uint16_t(Op::I64LtS):
+ CHECK(
+ EmitComparison(f, ValType::I64, JSOp::Lt, MCompare::Compare_Int64));
+ case uint16_t(Op::I64LtU):
+ CHECK(EmitComparison(f, ValType::I64, JSOp::Lt,
+ MCompare::Compare_UInt64));
+ case uint16_t(Op::I64GtS):
+ CHECK(
+ EmitComparison(f, ValType::I64, JSOp::Gt, MCompare::Compare_Int64));
+ case uint16_t(Op::I64GtU):
+ CHECK(EmitComparison(f, ValType::I64, JSOp::Gt,
+ MCompare::Compare_UInt64));
+ case uint16_t(Op::I64LeS):
+ CHECK(
+ EmitComparison(f, ValType::I64, JSOp::Le, MCompare::Compare_Int64));
+ case uint16_t(Op::I64LeU):
+ CHECK(EmitComparison(f, ValType::I64, JSOp::Le,
+ MCompare::Compare_UInt64));
+ case uint16_t(Op::I64GeS):
+ CHECK(
+ EmitComparison(f, ValType::I64, JSOp::Ge, MCompare::Compare_Int64));
+ case uint16_t(Op::I64GeU):
+ CHECK(EmitComparison(f, ValType::I64, JSOp::Ge,
+ MCompare::Compare_UInt64));
+ case uint16_t(Op::F32Eq):
+ CHECK(EmitComparison(f, ValType::F32, JSOp::Eq,
+ MCompare::Compare_Float32));
+ case uint16_t(Op::F32Ne):
+ CHECK(EmitComparison(f, ValType::F32, JSOp::Ne,
+ MCompare::Compare_Float32));
+ case uint16_t(Op::F32Lt):
+ CHECK(EmitComparison(f, ValType::F32, JSOp::Lt,
+ MCompare::Compare_Float32));
+ case uint16_t(Op::F32Gt):
+ CHECK(EmitComparison(f, ValType::F32, JSOp::Gt,
+ MCompare::Compare_Float32));
+ case uint16_t(Op::F32Le):
+ CHECK(EmitComparison(f, ValType::F32, JSOp::Le,
+ MCompare::Compare_Float32));
+ case uint16_t(Op::F32Ge):
+ CHECK(EmitComparison(f, ValType::F32, JSOp::Ge,
+ MCompare::Compare_Float32));
+ case uint16_t(Op::F64Eq):
+ CHECK(EmitComparison(f, ValType::F64, JSOp::Eq,
+ MCompare::Compare_Double));
+ case uint16_t(Op::F64Ne):
+ CHECK(EmitComparison(f, ValType::F64, JSOp::Ne,
+ MCompare::Compare_Double));
+ case uint16_t(Op::F64Lt):
+ CHECK(EmitComparison(f, ValType::F64, JSOp::Lt,
+ MCompare::Compare_Double));
+ case uint16_t(Op::F64Gt):
+ CHECK(EmitComparison(f, ValType::F64, JSOp::Gt,
+ MCompare::Compare_Double));
+ case uint16_t(Op::F64Le):
+ CHECK(EmitComparison(f, ValType::F64, JSOp::Le,
+ MCompare::Compare_Double));
+ case uint16_t(Op::F64Ge):
+ CHECK(EmitComparison(f, ValType::F64, JSOp::Ge,
+ MCompare::Compare_Double));
+
+ // Numeric operators
+ case uint16_t(Op::I32Clz):
+ CHECK(EmitUnaryWithType<MClz>(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32Ctz):
+ CHECK(EmitUnaryWithType<MCtz>(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32Popcnt):
+ CHECK(EmitUnaryWithType<MPopcnt>(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32Add):
+ CHECK(EmitAdd(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32Sub):
+ CHECK(EmitSub(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32Mul):
+ CHECK(EmitMul(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32DivS):
+ case uint16_t(Op::I32DivU):
+ CHECK(
+ EmitDiv(f, ValType::I32, MIRType::Int32, Op(op.b0) == Op::I32DivU));
+ case uint16_t(Op::I32RemS):
+ case uint16_t(Op::I32RemU):
+ CHECK(
+ EmitRem(f, ValType::I32, MIRType::Int32, Op(op.b0) == Op::I32RemU));
+ case uint16_t(Op::I32And):
+ CHECK(EmitBitwiseAndOrXor(f, ValType::I32, MIRType::Int32,
+ MWasmBinaryBitwise::SubOpcode::And));
+ case uint16_t(Op::I32Or):
+ CHECK(EmitBitwiseAndOrXor(f, ValType::I32, MIRType::Int32,
+ MWasmBinaryBitwise::SubOpcode::Or));
+ case uint16_t(Op::I32Xor):
+ CHECK(EmitBitwiseAndOrXor(f, ValType::I32, MIRType::Int32,
+ MWasmBinaryBitwise::SubOpcode::Xor));
+ case uint16_t(Op::I32Shl):
+ CHECK(EmitShift<MLsh>(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32ShrS):
+ CHECK(EmitShift<MRsh>(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32ShrU):
+ CHECK(EmitUrsh(f, ValType::I32, MIRType::Int32));
+ case uint16_t(Op::I32Rotl):
+ case uint16_t(Op::I32Rotr):
+ CHECK(EmitRotate(f, ValType::I32, Op(op.b0) == Op::I32Rotl));
+ case uint16_t(Op::I64Clz):
+ CHECK(EmitUnaryWithType<MClz>(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64Ctz):
+ CHECK(EmitUnaryWithType<MCtz>(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64Popcnt):
+ CHECK(EmitUnaryWithType<MPopcnt>(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64Add):
+ CHECK(EmitAdd(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64Sub):
+ CHECK(EmitSub(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64Mul):
+ CHECK(EmitMul(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64DivS):
+ case uint16_t(Op::I64DivU):
+ CHECK(
+ EmitDiv(f, ValType::I64, MIRType::Int64, Op(op.b0) == Op::I64DivU));
+ case uint16_t(Op::I64RemS):
+ case uint16_t(Op::I64RemU):
+ CHECK(
+ EmitRem(f, ValType::I64, MIRType::Int64, Op(op.b0) == Op::I64RemU));
+ case uint16_t(Op::I64And):
+ CHECK(EmitBitwiseAndOrXor(f, ValType::I64, MIRType::Int64,
+ MWasmBinaryBitwise::SubOpcode::And));
+ case uint16_t(Op::I64Or):
+ CHECK(EmitBitwiseAndOrXor(f, ValType::I64, MIRType::Int64,
+ MWasmBinaryBitwise::SubOpcode::Or));
+ case uint16_t(Op::I64Xor):
+ CHECK(EmitBitwiseAndOrXor(f, ValType::I64, MIRType::Int64,
+ MWasmBinaryBitwise::SubOpcode::Xor));
+ case uint16_t(Op::I64Shl):
+ CHECK(EmitShift<MLsh>(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64ShrS):
+ CHECK(EmitShift<MRsh>(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64ShrU):
+ CHECK(EmitUrsh(f, ValType::I64, MIRType::Int64));
+ case uint16_t(Op::I64Rotl):
+ case uint16_t(Op::I64Rotr):
+ CHECK(EmitRotate(f, ValType::I64, Op(op.b0) == Op::I64Rotl));
+ case uint16_t(Op::F32Abs):
+ CHECK(EmitUnaryWithType<MAbs>(f, ValType::F32, MIRType::Float32));
+ case uint16_t(Op::F32Neg):
+ CHECK(EmitUnaryWithType<MWasmNeg>(f, ValType::F32, MIRType::Float32));
+ case uint16_t(Op::F32Ceil):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigCeilF));
+ case uint16_t(Op::F32Floor):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigFloorF));
+ case uint16_t(Op::F32Trunc):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigTruncF));
+ case uint16_t(Op::F32Nearest):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigNearbyIntF));
+ case uint16_t(Op::F32Sqrt):
+ CHECK(EmitUnaryWithType<MSqrt>(f, ValType::F32, MIRType::Float32));
+ case uint16_t(Op::F32Add):
+ CHECK(EmitAdd(f, ValType::F32, MIRType::Float32));
+ case uint16_t(Op::F32Sub):
+ CHECK(EmitSub(f, ValType::F32, MIRType::Float32));
+ case uint16_t(Op::F32Mul):
+ CHECK(EmitMul(f, ValType::F32, MIRType::Float32));
+ case uint16_t(Op::F32Div):
+ CHECK(EmitDiv(f, ValType::F32, MIRType::Float32,
+ /* isUnsigned = */ false));
+ case uint16_t(Op::F32Min):
+ case uint16_t(Op::F32Max):
+ CHECK(EmitMinMax(f, ValType::F32, MIRType::Float32,
+ Op(op.b0) == Op::F32Max));
+ case uint16_t(Op::F32CopySign):
+ CHECK(EmitCopySign(f, ValType::F32));
+ case uint16_t(Op::F64Abs):
+ CHECK(EmitUnaryWithType<MAbs>(f, ValType::F64, MIRType::Double));
+ case uint16_t(Op::F64Neg):
+ CHECK(EmitUnaryWithType<MWasmNeg>(f, ValType::F64, MIRType::Double));
+ case uint16_t(Op::F64Ceil):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigCeilD));
+ case uint16_t(Op::F64Floor):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigFloorD));
+ case uint16_t(Op::F64Trunc):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigTruncD));
+ case uint16_t(Op::F64Nearest):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigNearbyIntD));
+ case uint16_t(Op::F64Sqrt):
+ CHECK(EmitUnaryWithType<MSqrt>(f, ValType::F64, MIRType::Double));
+ case uint16_t(Op::F64Add):
+ CHECK(EmitAdd(f, ValType::F64, MIRType::Double));
+ case uint16_t(Op::F64Sub):
+ CHECK(EmitSub(f, ValType::F64, MIRType::Double));
+ case uint16_t(Op::F64Mul):
+ CHECK(EmitMul(f, ValType::F64, MIRType::Double));
+ case uint16_t(Op::F64Div):
+ CHECK(EmitDiv(f, ValType::F64, MIRType::Double,
+ /* isUnsigned = */ false));
+ case uint16_t(Op::F64Min):
+ case uint16_t(Op::F64Max):
+ CHECK(EmitMinMax(f, ValType::F64, MIRType::Double,
+ Op(op.b0) == Op::F64Max));
+ case uint16_t(Op::F64CopySign):
+ CHECK(EmitCopySign(f, ValType::F64));
+
+ // Conversions
+ case uint16_t(Op::I32WrapI64):
+ CHECK(EmitConversion<MWrapInt64ToInt32>(f, ValType::I64, ValType::I32));
+ case uint16_t(Op::I32TruncF32S):
+ case uint16_t(Op::I32TruncF32U):
+ CHECK(EmitTruncate(f, ValType::F32, ValType::I32,
+ Op(op.b0) == Op::I32TruncF32U, false));
+ case uint16_t(Op::I32TruncF64S):
+ case uint16_t(Op::I32TruncF64U):
+ CHECK(EmitTruncate(f, ValType::F64, ValType::I32,
+ Op(op.b0) == Op::I32TruncF64U, false));
+ case uint16_t(Op::I64ExtendI32S):
+ case uint16_t(Op::I64ExtendI32U):
+ CHECK(EmitExtendI32(f, Op(op.b0) == Op::I64ExtendI32U));
+ case uint16_t(Op::I64TruncF32S):
+ case uint16_t(Op::I64TruncF32U):
+ CHECK(EmitTruncate(f, ValType::F32, ValType::I64,
+ Op(op.b0) == Op::I64TruncF32U, false));
+ case uint16_t(Op::I64TruncF64S):
+ case uint16_t(Op::I64TruncF64U):
+ CHECK(EmitTruncate(f, ValType::F64, ValType::I64,
+ Op(op.b0) == Op::I64TruncF64U, false));
+ case uint16_t(Op::F32ConvertI32S):
+ CHECK(EmitConversion<MToFloat32>(f, ValType::I32, ValType::F32));
+ case uint16_t(Op::F32ConvertI32U):
+ CHECK(EmitConversion<MWasmUnsignedToFloat32>(f, ValType::I32,
+ ValType::F32));
+ case uint16_t(Op::F32ConvertI64S):
+ case uint16_t(Op::F32ConvertI64U):
+ CHECK(EmitConvertI64ToFloatingPoint(f, ValType::F32, MIRType::Float32,
+ Op(op.b0) == Op::F32ConvertI64U));
+ case uint16_t(Op::F32DemoteF64):
+ CHECK(EmitConversion<MToFloat32>(f, ValType::F64, ValType::F32));
+ case uint16_t(Op::F64ConvertI32S):
+ CHECK(EmitConversion<MToDouble>(f, ValType::I32, ValType::F64));
+ case uint16_t(Op::F64ConvertI32U):
+ CHECK(EmitConversion<MWasmUnsignedToDouble>(f, ValType::I32,
+ ValType::F64));
+ case uint16_t(Op::F64ConvertI64S):
+ case uint16_t(Op::F64ConvertI64U):
+ CHECK(EmitConvertI64ToFloatingPoint(f, ValType::F64, MIRType::Double,
+ Op(op.b0) == Op::F64ConvertI64U));
+ case uint16_t(Op::F64PromoteF32):
+ CHECK(EmitConversion<MToDouble>(f, ValType::F32, ValType::F64));
+
+ // Reinterpretations
+ case uint16_t(Op::I32ReinterpretF32):
+ CHECK(EmitReinterpret(f, ValType::I32, ValType::F32, MIRType::Int32));
+ case uint16_t(Op::I64ReinterpretF64):
+ CHECK(EmitReinterpret(f, ValType::I64, ValType::F64, MIRType::Int64));
+ case uint16_t(Op::F32ReinterpretI32):
+ CHECK(EmitReinterpret(f, ValType::F32, ValType::I32, MIRType::Float32));
+ case uint16_t(Op::F64ReinterpretI64):
+ CHECK(EmitReinterpret(f, ValType::F64, ValType::I64, MIRType::Double));
+
+#ifdef ENABLE_WASM_GC
+ case uint16_t(Op::RefEq):
+ if (!f.moduleEnv().gcEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitComparison(f, RefType::eq(), JSOp::Eq,
+ MCompare::Compare_RefOrNull));
+#endif
+ case uint16_t(Op::RefFunc):
+ CHECK(EmitRefFunc(f));
+ case uint16_t(Op::RefNull):
+ CHECK(EmitRefNull(f));
+ case uint16_t(Op::RefIsNull):
+ CHECK(EmitRefIsNull(f));
+
+ // Sign extensions
+ case uint16_t(Op::I32Extend8S):
+ CHECK(EmitSignExtend(f, 1, 4));
+ case uint16_t(Op::I32Extend16S):
+ CHECK(EmitSignExtend(f, 2, 4));
+ case uint16_t(Op::I64Extend8S):
+ CHECK(EmitSignExtend(f, 1, 8));
+ case uint16_t(Op::I64Extend16S):
+ CHECK(EmitSignExtend(f, 2, 8));
+ case uint16_t(Op::I64Extend32S):
+ CHECK(EmitSignExtend(f, 4, 8));
+
+#ifdef ENABLE_WASM_FUNCTION_REFERENCES
+ case uint16_t(Op::RefAsNonNull):
+ if (!f.moduleEnv().functionReferencesEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitRefAsNonNull(f));
+ case uint16_t(Op::BrOnNull): {
+ if (!f.moduleEnv().functionReferencesEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitBrOnNull(f));
+ }
+ case uint16_t(Op::BrOnNonNull): {
+ if (!f.moduleEnv().functionReferencesEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitBrOnNonNull(f));
+ }
+ case uint16_t(Op::CallRef): {
+ if (!f.moduleEnv().functionReferencesEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitCallRef(f));
+ }
+#endif
+
+ // Gc operations
+#ifdef ENABLE_WASM_GC
+ case uint16_t(Op::GcPrefix): {
+ if (!f.moduleEnv().gcEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ switch (op.b1) {
+ case uint32_t(GcOp::StructNew):
+ CHECK(EmitStructNew(f));
+ case uint32_t(GcOp::StructNewDefault):
+ CHECK(EmitStructNewDefault(f));
+ case uint32_t(GcOp::StructSet):
+ CHECK(EmitStructSet(f));
+ case uint32_t(GcOp::StructGet):
+ CHECK(EmitStructGet(f, FieldWideningOp::None));
+ case uint32_t(GcOp::StructGetS):
+ CHECK(EmitStructGet(f, FieldWideningOp::Signed));
+ case uint32_t(GcOp::StructGetU):
+ CHECK(EmitStructGet(f, FieldWideningOp::Unsigned));
+ case uint32_t(GcOp::ArrayNew):
+ CHECK(EmitArrayNew(f));
+ case uint32_t(GcOp::ArrayNewDefault):
+ CHECK(EmitArrayNewDefault(f));
+ case uint32_t(GcOp::ArrayNewFixed):
+ CHECK(EmitArrayNewFixed(f));
+ case uint32_t(GcOp::ArrayNewData):
+ CHECK(EmitArrayNewData(f));
+ case uint32_t(GcOp::ArrayInitFromElemStaticV5):
+ case uint32_t(GcOp::ArrayNewElem):
+ CHECK(EmitArrayNewElem(f));
+ case uint32_t(GcOp::ArraySet):
+ CHECK(EmitArraySet(f));
+ case uint32_t(GcOp::ArrayGet):
+ CHECK(EmitArrayGet(f, FieldWideningOp::None));
+ case uint32_t(GcOp::ArrayGetS):
+ CHECK(EmitArrayGet(f, FieldWideningOp::Signed));
+ case uint32_t(GcOp::ArrayGetU):
+ CHECK(EmitArrayGet(f, FieldWideningOp::Unsigned));
+ case uint32_t(GcOp::ArrayLenWithTypeIndex):
+ CHECK(EmitArrayLen(f, /*decodeIgnoredTypeIndex=*/true));
+ case uint32_t(GcOp::ArrayLen):
+ CHECK(EmitArrayLen(f, /*decodeIgnoredTypeIndex=*/false));
+ case uint32_t(GcOp::ArrayCopy):
+ CHECK(EmitArrayCopy(f));
+ case uint32_t(GcOp::RefTestV5):
+ CHECK(EmitRefTestV5(f));
+ case uint32_t(GcOp::RefCastV5):
+ CHECK(EmitRefCastV5(f));
+ case uint32_t(GcOp::BrOnCast):
+ CHECK(EmitBrOnCast(f));
+ case uint32_t(GcOp::BrOnCastV5):
+ CHECK(EmitBrOnCastCommonV5(f, /*onSuccess=*/true));
+ case uint32_t(GcOp::BrOnCastFailV5):
+ CHECK(EmitBrOnCastCommonV5(f, /*onSuccess=*/false));
+ case uint32_t(GcOp::BrOnCastHeapV5):
+ CHECK(
+ EmitBrOnCastHeapV5(f, /*onSuccess=*/true, /*nullable=*/false));
+ case uint32_t(GcOp::BrOnCastHeapNullV5):
+ CHECK(EmitBrOnCastHeapV5(f, /*onSuccess=*/true, /*nullable=*/true));
+ case uint32_t(GcOp::BrOnCastFailHeapV5):
+ CHECK(
+ EmitBrOnCastHeapV5(f, /*onSuccess=*/false, /*nullable=*/false));
+ case uint32_t(GcOp::BrOnCastFailHeapNullV5):
+ CHECK(
+ EmitBrOnCastHeapV5(f, /*onSuccess=*/false, /*nullable=*/true));
+ case uint32_t(GcOp::RefAsStructV5):
+ CHECK(EmitRefAsStructV5(f));
+ case uint32_t(GcOp::BrOnNonStructV5):
+ CHECK(EmitBrOnNonStructV5(f));
+ case uint32_t(GcOp::RefTest):
+ CHECK(EmitRefTest(f, /*nullable=*/false));
+ case uint32_t(GcOp::RefTestNull):
+ CHECK(EmitRefTest(f, /*nullable=*/true));
+ case uint32_t(GcOp::RefCast):
+ CHECK(EmitRefCast(f, /*nullable=*/false));
+ case uint32_t(GcOp::RefCastNull):
+ CHECK(EmitRefCast(f, /*nullable=*/true));
+ case uint16_t(GcOp::ExternInternalize):
+ CHECK(EmitExternInternalize(f));
+ case uint16_t(GcOp::ExternExternalize):
+ CHECK(EmitExternExternalize(f));
+ default:
+ return f.iter().unrecognizedOpcode(&op);
+ } // switch (op.b1)
+ break;
+ }
+#endif
+
+ // SIMD operations
+#ifdef ENABLE_WASM_SIMD
+ case uint16_t(Op::SimdPrefix): {
+ if (!f.moduleEnv().simdAvailable()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ switch (op.b1) {
+ case uint32_t(SimdOp::V128Const):
+ CHECK(EmitConstSimd128(f));
+ case uint32_t(SimdOp::V128Load):
+ CHECK(EmitLoad(f, ValType::V128, Scalar::Simd128));
+ case uint32_t(SimdOp::V128Store):
+ CHECK(EmitStore(f, 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(f, /* 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(f, /* commutative= */ false, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I8x16Splat):
+ case uint32_t(SimdOp::I16x8Splat):
+ case uint32_t(SimdOp::I32x4Splat):
+ CHECK(EmitSplatSimd128(f, ValType::I32, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I64x2Splat):
+ CHECK(EmitSplatSimd128(f, ValType::I64, SimdOp(op.b1)));
+ case uint32_t(SimdOp::F32x4Splat):
+ CHECK(EmitSplatSimd128(f, ValType::F32, SimdOp(op.b1)));
+ case uint32_t(SimdOp::F64x2Splat):
+ CHECK(EmitSplatSimd128(f, 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(f, 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(f, 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(f, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I8x16ExtractLaneS):
+ case uint32_t(SimdOp::I8x16ExtractLaneU):
+ CHECK(EmitExtractLaneSimd128(f, ValType::I32, 16, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I16x8ExtractLaneS):
+ case uint32_t(SimdOp::I16x8ExtractLaneU):
+ CHECK(EmitExtractLaneSimd128(f, ValType::I32, 8, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I32x4ExtractLane):
+ CHECK(EmitExtractLaneSimd128(f, ValType::I32, 4, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I64x2ExtractLane):
+ CHECK(EmitExtractLaneSimd128(f, ValType::I64, 2, SimdOp(op.b1)));
+ case uint32_t(SimdOp::F32x4ExtractLane):
+ CHECK(EmitExtractLaneSimd128(f, ValType::F32, 4, SimdOp(op.b1)));
+ case uint32_t(SimdOp::F64x2ExtractLane):
+ CHECK(EmitExtractLaneSimd128(f, ValType::F64, 2, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I8x16ReplaceLane):
+ CHECK(EmitReplaceLaneSimd128(f, ValType::I32, 16, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I16x8ReplaceLane):
+ CHECK(EmitReplaceLaneSimd128(f, ValType::I32, 8, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I32x4ReplaceLane):
+ CHECK(EmitReplaceLaneSimd128(f, ValType::I32, 4, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I64x2ReplaceLane):
+ CHECK(EmitReplaceLaneSimd128(f, ValType::I64, 2, SimdOp(op.b1)));
+ case uint32_t(SimdOp::F32x4ReplaceLane):
+ CHECK(EmitReplaceLaneSimd128(f, ValType::F32, 4, SimdOp(op.b1)));
+ case uint32_t(SimdOp::F64x2ReplaceLane):
+ CHECK(EmitReplaceLaneSimd128(f, ValType::F64, 2, SimdOp(op.b1)));
+ case uint32_t(SimdOp::V128Bitselect):
+ CHECK(EmitTernarySimd128(f, SimdOp(op.b1)));
+ case uint32_t(SimdOp::I8x16Shuffle):
+ CHECK(EmitShuffleSimd128(f));
+ case uint32_t(SimdOp::V128Load8Splat):
+ CHECK(EmitLoadSplatSimd128(f, Scalar::Uint8, SimdOp::I8x16Splat));
+ case uint32_t(SimdOp::V128Load16Splat):
+ CHECK(EmitLoadSplatSimd128(f, Scalar::Uint16, SimdOp::I16x8Splat));
+ case uint32_t(SimdOp::V128Load32Splat):
+ CHECK(EmitLoadSplatSimd128(f, Scalar::Float32, SimdOp::I32x4Splat));
+ case uint32_t(SimdOp::V128Load64Splat):
+ CHECK(EmitLoadSplatSimd128(f, 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(f, SimdOp(op.b1)));
+ case uint32_t(SimdOp::V128Load32Zero):
+ CHECK(EmitLoadZeroSimd128(f, Scalar::Float32, 4));
+ case uint32_t(SimdOp::V128Load64Zero):
+ CHECK(EmitLoadZeroSimd128(f, Scalar::Float64, 8));
+ case uint32_t(SimdOp::V128Load8Lane):
+ CHECK(EmitLoadLaneSimd128(f, 1));
+ case uint32_t(SimdOp::V128Load16Lane):
+ CHECK(EmitLoadLaneSimd128(f, 2));
+ case uint32_t(SimdOp::V128Load32Lane):
+ CHECK(EmitLoadLaneSimd128(f, 4));
+ case uint32_t(SimdOp::V128Load64Lane):
+ CHECK(EmitLoadLaneSimd128(f, 8));
+ case uint32_t(SimdOp::V128Store8Lane):
+ CHECK(EmitStoreLaneSimd128(f, 1));
+ case uint32_t(SimdOp::V128Store16Lane):
+ CHECK(EmitStoreLaneSimd128(f, 2));
+ case uint32_t(SimdOp::V128Store32Lane):
+ CHECK(EmitStoreLaneSimd128(f, 4));
+ case uint32_t(SimdOp::V128Store64Lane):
+ CHECK(EmitStoreLaneSimd128(f, 8));
+# ifdef ENABLE_WASM_RELAXED_SIMD
+ case uint32_t(SimdOp::F32x4RelaxedFma):
+ case uint32_t(SimdOp::F32x4RelaxedFnma):
+ case uint32_t(SimdOp::F64x2RelaxedFma):
+ case uint32_t(SimdOp::F64x2RelaxedFnma):
+ case uint32_t(SimdOp::I8x16RelaxedLaneSelect):
+ case uint32_t(SimdOp::I16x8RelaxedLaneSelect):
+ case uint32_t(SimdOp::I32x4RelaxedLaneSelect):
+ case uint32_t(SimdOp::I64x2RelaxedLaneSelect):
+ case uint32_t(SimdOp::I32x4DotI8x16I7x16AddS): {
+ if (!f.moduleEnv().v128RelaxedEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitTernarySimd128(f, SimdOp(op.b1)));
+ }
+ case uint32_t(SimdOp::F32x4RelaxedMin):
+ case uint32_t(SimdOp::F32x4RelaxedMax):
+ case uint32_t(SimdOp::F64x2RelaxedMin):
+ case uint32_t(SimdOp::F64x2RelaxedMax):
+ case uint32_t(SimdOp::I16x8RelaxedQ15MulrS): {
+ if (!f.moduleEnv().v128RelaxedEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitBinarySimd128(f, /* 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 (!f.moduleEnv().v128RelaxedEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitUnarySimd128(f, SimdOp(op.b1)));
+ }
+ case uint32_t(SimdOp::I8x16RelaxedSwizzle):
+ case uint32_t(SimdOp::I16x8DotI8x16I7x16S): {
+ if (!f.moduleEnv().v128RelaxedEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(
+ EmitBinarySimd128(f, /* commutative= */ false, SimdOp(op.b1)));
+ }
+# endif
+
+ default:
+ return f.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(f, ValType::F32, ValType::I32,
+ MiscOp(op.b1) == MiscOp::I32TruncSatF32U, true));
+ case uint32_t(MiscOp::I32TruncSatF64S):
+ case uint32_t(MiscOp::I32TruncSatF64U):
+ CHECK(EmitTruncate(f, ValType::F64, ValType::I32,
+ MiscOp(op.b1) == MiscOp::I32TruncSatF64U, true));
+ case uint32_t(MiscOp::I64TruncSatF32S):
+ case uint32_t(MiscOp::I64TruncSatF32U):
+ CHECK(EmitTruncate(f, ValType::F32, ValType::I64,
+ MiscOp(op.b1) == MiscOp::I64TruncSatF32U, true));
+ case uint32_t(MiscOp::I64TruncSatF64S):
+ case uint32_t(MiscOp::I64TruncSatF64U):
+ CHECK(EmitTruncate(f, ValType::F64, ValType::I64,
+ MiscOp(op.b1) == MiscOp::I64TruncSatF64U, true));
+ case uint32_t(MiscOp::MemoryCopy):
+ CHECK(EmitMemCopy(f));
+ case uint32_t(MiscOp::DataDrop):
+ CHECK(EmitDataOrElemDrop(f, /*isData=*/true));
+ case uint32_t(MiscOp::MemoryFill):
+ CHECK(EmitMemFill(f));
+ case uint32_t(MiscOp::MemoryInit):
+ CHECK(EmitMemOrTableInit(f, /*isMem=*/true));
+ case uint32_t(MiscOp::TableCopy):
+ CHECK(EmitTableCopy(f));
+ case uint32_t(MiscOp::ElemDrop):
+ CHECK(EmitDataOrElemDrop(f, /*isData=*/false));
+ case uint32_t(MiscOp::TableInit):
+ CHECK(EmitMemOrTableInit(f, /*isMem=*/false));
+ case uint32_t(MiscOp::TableFill):
+ CHECK(EmitTableFill(f));
+#if ENABLE_WASM_MEMORY_CONTROL
+ case uint32_t(MiscOp::MemoryDiscard): {
+ if (!f.moduleEnv().memoryControlEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitMemDiscard(f));
+ }
+#endif
+ case uint32_t(MiscOp::TableGrow):
+ CHECK(EmitTableGrow(f));
+ case uint32_t(MiscOp::TableSize):
+ CHECK(EmitTableSize(f));
+ default:
+ return f.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 (f.moduleEnv().sharedMemoryEnabled() == Shareable::False) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ switch (op.b1) {
+ case uint32_t(ThreadOp::Wake):
+ CHECK(EmitWake(f));
+
+ case uint32_t(ThreadOp::I32Wait):
+ CHECK(EmitWait(f, ValType::I32, 4));
+ case uint32_t(ThreadOp::I64Wait):
+ CHECK(EmitWait(f, ValType::I64, 8));
+ case uint32_t(ThreadOp::Fence):
+ CHECK(EmitFence(f));
+
+ case uint32_t(ThreadOp::I32AtomicLoad):
+ CHECK(EmitAtomicLoad(f, ValType::I32, Scalar::Int32));
+ case uint32_t(ThreadOp::I64AtomicLoad):
+ CHECK(EmitAtomicLoad(f, ValType::I64, Scalar::Int64));
+ case uint32_t(ThreadOp::I32AtomicLoad8U):
+ CHECK(EmitAtomicLoad(f, ValType::I32, Scalar::Uint8));
+ case uint32_t(ThreadOp::I32AtomicLoad16U):
+ CHECK(EmitAtomicLoad(f, ValType::I32, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicLoad8U):
+ CHECK(EmitAtomicLoad(f, ValType::I64, Scalar::Uint8));
+ case uint32_t(ThreadOp::I64AtomicLoad16U):
+ CHECK(EmitAtomicLoad(f, ValType::I64, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicLoad32U):
+ CHECK(EmitAtomicLoad(f, ValType::I64, Scalar::Uint32));
+
+ case uint32_t(ThreadOp::I32AtomicStore):
+ CHECK(EmitAtomicStore(f, ValType::I32, Scalar::Int32));
+ case uint32_t(ThreadOp::I64AtomicStore):
+ CHECK(EmitAtomicStore(f, ValType::I64, Scalar::Int64));
+ case uint32_t(ThreadOp::I32AtomicStore8U):
+ CHECK(EmitAtomicStore(f, ValType::I32, Scalar::Uint8));
+ case uint32_t(ThreadOp::I32AtomicStore16U):
+ CHECK(EmitAtomicStore(f, ValType::I32, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicStore8U):
+ CHECK(EmitAtomicStore(f, ValType::I64, Scalar::Uint8));
+ case uint32_t(ThreadOp::I64AtomicStore16U):
+ CHECK(EmitAtomicStore(f, ValType::I64, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicStore32U):
+ CHECK(EmitAtomicStore(f, ValType::I64, Scalar::Uint32));
+
+ case uint32_t(ThreadOp::I32AtomicAdd):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Int32,
+ AtomicFetchAddOp));
+ case uint32_t(ThreadOp::I64AtomicAdd):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Int64,
+ AtomicFetchAddOp));
+ case uint32_t(ThreadOp::I32AtomicAdd8U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint8,
+ AtomicFetchAddOp));
+ case uint32_t(ThreadOp::I32AtomicAdd16U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint16,
+ AtomicFetchAddOp));
+ case uint32_t(ThreadOp::I64AtomicAdd8U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint8,
+ AtomicFetchAddOp));
+ case uint32_t(ThreadOp::I64AtomicAdd16U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint16,
+ AtomicFetchAddOp));
+ case uint32_t(ThreadOp::I64AtomicAdd32U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint32,
+ AtomicFetchAddOp));
+
+ case uint32_t(ThreadOp::I32AtomicSub):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Int32,
+ AtomicFetchSubOp));
+ case uint32_t(ThreadOp::I64AtomicSub):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Int64,
+ AtomicFetchSubOp));
+ case uint32_t(ThreadOp::I32AtomicSub8U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint8,
+ AtomicFetchSubOp));
+ case uint32_t(ThreadOp::I32AtomicSub16U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint16,
+ AtomicFetchSubOp));
+ case uint32_t(ThreadOp::I64AtomicSub8U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint8,
+ AtomicFetchSubOp));
+ case uint32_t(ThreadOp::I64AtomicSub16U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint16,
+ AtomicFetchSubOp));
+ case uint32_t(ThreadOp::I64AtomicSub32U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint32,
+ AtomicFetchSubOp));
+
+ case uint32_t(ThreadOp::I32AtomicAnd):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Int32,
+ AtomicFetchAndOp));
+ case uint32_t(ThreadOp::I64AtomicAnd):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Int64,
+ AtomicFetchAndOp));
+ case uint32_t(ThreadOp::I32AtomicAnd8U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint8,
+ AtomicFetchAndOp));
+ case uint32_t(ThreadOp::I32AtomicAnd16U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint16,
+ AtomicFetchAndOp));
+ case uint32_t(ThreadOp::I64AtomicAnd8U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint8,
+ AtomicFetchAndOp));
+ case uint32_t(ThreadOp::I64AtomicAnd16U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint16,
+ AtomicFetchAndOp));
+ case uint32_t(ThreadOp::I64AtomicAnd32U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint32,
+ AtomicFetchAndOp));
+
+ case uint32_t(ThreadOp::I32AtomicOr):
+ CHECK(
+ EmitAtomicRMW(f, ValType::I32, Scalar::Int32, AtomicFetchOrOp));
+ case uint32_t(ThreadOp::I64AtomicOr):
+ CHECK(
+ EmitAtomicRMW(f, ValType::I64, Scalar::Int64, AtomicFetchOrOp));
+ case uint32_t(ThreadOp::I32AtomicOr8U):
+ CHECK(
+ EmitAtomicRMW(f, ValType::I32, Scalar::Uint8, AtomicFetchOrOp));
+ case uint32_t(ThreadOp::I32AtomicOr16U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint16,
+ AtomicFetchOrOp));
+ case uint32_t(ThreadOp::I64AtomicOr8U):
+ CHECK(
+ EmitAtomicRMW(f, ValType::I64, Scalar::Uint8, AtomicFetchOrOp));
+ case uint32_t(ThreadOp::I64AtomicOr16U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint16,
+ AtomicFetchOrOp));
+ case uint32_t(ThreadOp::I64AtomicOr32U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint32,
+ AtomicFetchOrOp));
+
+ case uint32_t(ThreadOp::I32AtomicXor):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Int32,
+ AtomicFetchXorOp));
+ case uint32_t(ThreadOp::I64AtomicXor):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Int64,
+ AtomicFetchXorOp));
+ case uint32_t(ThreadOp::I32AtomicXor8U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint8,
+ AtomicFetchXorOp));
+ case uint32_t(ThreadOp::I32AtomicXor16U):
+ CHECK(EmitAtomicRMW(f, ValType::I32, Scalar::Uint16,
+ AtomicFetchXorOp));
+ case uint32_t(ThreadOp::I64AtomicXor8U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint8,
+ AtomicFetchXorOp));
+ case uint32_t(ThreadOp::I64AtomicXor16U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint16,
+ AtomicFetchXorOp));
+ case uint32_t(ThreadOp::I64AtomicXor32U):
+ CHECK(EmitAtomicRMW(f, ValType::I64, Scalar::Uint32,
+ AtomicFetchXorOp));
+
+ case uint32_t(ThreadOp::I32AtomicXchg):
+ CHECK(EmitAtomicXchg(f, ValType::I32, Scalar::Int32));
+ case uint32_t(ThreadOp::I64AtomicXchg):
+ CHECK(EmitAtomicXchg(f, ValType::I64, Scalar::Int64));
+ case uint32_t(ThreadOp::I32AtomicXchg8U):
+ CHECK(EmitAtomicXchg(f, ValType::I32, Scalar::Uint8));
+ case uint32_t(ThreadOp::I32AtomicXchg16U):
+ CHECK(EmitAtomicXchg(f, ValType::I32, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicXchg8U):
+ CHECK(EmitAtomicXchg(f, ValType::I64, Scalar::Uint8));
+ case uint32_t(ThreadOp::I64AtomicXchg16U):
+ CHECK(EmitAtomicXchg(f, ValType::I64, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicXchg32U):
+ CHECK(EmitAtomicXchg(f, ValType::I64, Scalar::Uint32));
+
+ case uint32_t(ThreadOp::I32AtomicCmpXchg):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I32, Scalar::Int32));
+ case uint32_t(ThreadOp::I64AtomicCmpXchg):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I64, Scalar::Int64));
+ case uint32_t(ThreadOp::I32AtomicCmpXchg8U):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I32, Scalar::Uint8));
+ case uint32_t(ThreadOp::I32AtomicCmpXchg16U):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I32, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicCmpXchg8U):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I64, Scalar::Uint8));
+ case uint32_t(ThreadOp::I64AtomicCmpXchg16U):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I64, Scalar::Uint16));
+ case uint32_t(ThreadOp::I64AtomicCmpXchg32U):
+ CHECK(EmitAtomicCmpXchg(f, ValType::I64, Scalar::Uint32));
+
+ default:
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ break;
+ }
+
+ // asm.js-specific operators
+ case uint16_t(Op::MozPrefix): {
+ if (op.b1 == uint32_t(MozOp::Intrinsic)) {
+ if (!f.moduleEnv().intrinsicsEnabled()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ CHECK(EmitIntrinsic(f));
+ }
+
+ if (!f.moduleEnv().isAsmJS()) {
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ switch (op.b1) {
+ case uint32_t(MozOp::TeeGlobal):
+ CHECK(EmitTeeGlobal(f));
+ case uint32_t(MozOp::I32Min):
+ case uint32_t(MozOp::I32Max):
+ CHECK(EmitMinMax(f, ValType::I32, MIRType::Int32,
+ MozOp(op.b1) == MozOp::I32Max));
+ case uint32_t(MozOp::I32Neg):
+ CHECK(EmitUnaryWithType<MWasmNeg>(f, ValType::I32, MIRType::Int32));
+ case uint32_t(MozOp::I32BitNot):
+ CHECK(EmitBitNot(f, ValType::I32));
+ case uint32_t(MozOp::I32Abs):
+ CHECK(EmitUnaryWithType<MAbs>(f, ValType::I32, MIRType::Int32));
+ case uint32_t(MozOp::F32TeeStoreF64):
+ CHECK(EmitTeeStoreWithCoercion(f, ValType::F32, Scalar::Float64));
+ case uint32_t(MozOp::F64TeeStoreF32):
+ CHECK(EmitTeeStoreWithCoercion(f, ValType::F64, Scalar::Float32));
+ case uint32_t(MozOp::I32TeeStore8):
+ CHECK(EmitTeeStore(f, ValType::I32, Scalar::Int8));
+ case uint32_t(MozOp::I32TeeStore16):
+ CHECK(EmitTeeStore(f, ValType::I32, Scalar::Int16));
+ case uint32_t(MozOp::I64TeeStore8):
+ CHECK(EmitTeeStore(f, ValType::I64, Scalar::Int8));
+ case uint32_t(MozOp::I64TeeStore16):
+ CHECK(EmitTeeStore(f, ValType::I64, Scalar::Int16));
+ case uint32_t(MozOp::I64TeeStore32):
+ CHECK(EmitTeeStore(f, ValType::I64, Scalar::Int32));
+ case uint32_t(MozOp::I32TeeStore):
+ CHECK(EmitTeeStore(f, ValType::I32, Scalar::Int32));
+ case uint32_t(MozOp::I64TeeStore):
+ CHECK(EmitTeeStore(f, ValType::I64, Scalar::Int64));
+ case uint32_t(MozOp::F32TeeStore):
+ CHECK(EmitTeeStore(f, ValType::F32, Scalar::Float32));
+ case uint32_t(MozOp::F64TeeStore):
+ CHECK(EmitTeeStore(f, ValType::F64, Scalar::Float64));
+ case uint32_t(MozOp::F64Mod):
+ CHECK(EmitRem(f, ValType::F64, MIRType::Double,
+ /* isUnsigned = */ false));
+ case uint32_t(MozOp::F64SinNative):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigSinNativeD));
+ case uint32_t(MozOp::F64SinFdlibm):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigSinFdlibmD));
+ case uint32_t(MozOp::F64CosNative):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigCosNativeD));
+ case uint32_t(MozOp::F64CosFdlibm):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigCosFdlibmD));
+ case uint32_t(MozOp::F64TanNative):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigTanNativeD));
+ case uint32_t(MozOp::F64TanFdlibm):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigTanFdlibmD));
+ case uint32_t(MozOp::F64Asin):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigASinD));
+ case uint32_t(MozOp::F64Acos):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigACosD));
+ case uint32_t(MozOp::F64Atan):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigATanD));
+ case uint32_t(MozOp::F64Exp):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigExpD));
+ case uint32_t(MozOp::F64Log):
+ CHECK(EmitUnaryMathBuiltinCall(f, SASigLogD));
+ case uint32_t(MozOp::F64Pow):
+ CHECK(EmitBinaryMathBuiltinCall(f, SASigPowD));
+ case uint32_t(MozOp::F64Atan2):
+ CHECK(EmitBinaryMathBuiltinCall(f, SASigATan2D));
+ case uint32_t(MozOp::OldCallDirect):
+ CHECK(EmitCall(f, /* asmJSFuncDef = */ true));
+ case uint32_t(MozOp::OldCallIndirect):
+ CHECK(EmitCallIndirect(f, /* oldStyle = */ true));
+
+ default:
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ break;
+ }
+
+ default:
+ return f.iter().unrecognizedOpcode(&op);
+ }
+ }
+
+ MOZ_CRASH("unreachable");
+
+#undef CHECK
+}
+
+bool wasm::IonCompileFunctions(const ModuleEnvironment& moduleEnv,
+ const CompilerEnvironment& compilerEnv,
+ LifoAlloc& lifo,
+ const FuncCompileInputVector& inputs,
+ CompiledCode* code, UniqueChars* error) {
+ MOZ_ASSERT(compilerEnv.tier() == Tier::Optimized);
+ MOZ_ASSERT(compilerEnv.debug() == DebugEnabled::False);
+
+ TempAllocator alloc(&lifo);
+ JitContext jitContext;
+ MOZ_ASSERT(IsCompilingWasm());
+ WasmMacroAssembler masm(alloc, moduleEnv);
+#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) {
+ JitSpewCont(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.lineOrBytecode, error);
+
+ // Build the local types vector.
+
+ const FuncType& funcType = *moduleEnv.funcs[func.index].type;
+ ValTypeVector locals;
+ if (!locals.appendAll(funcType.args())) {
+ return false;
+ }
+ if (!DecodeLocalEntries(d, *moduleEnv.types, moduleEnv.features, &locals)) {
+ return false;
+ }
+
+ // Set up for Ion compilation.
+
+ const JitCompileOptions options;
+ MIRGraph graph(&alloc);
+ CompileInfo compileInfo(locals.length());
+ MIRGenerator mir(nullptr, options, &alloc, &graph, &compileInfo,
+ IonOptimizations.get(OptimizationLevel::Wasm));
+ if (moduleEnv.usesMemory()) {
+ if (moduleEnv.memory->indexType() == IndexType::I32) {
+ mir.initMinWasmHeapLength(moduleEnv.memory->initialLength32());
+ } else {
+ mir.initMinWasmHeapLength(moduleEnv.memory->initialLength64());
+ }
+ }
+
+ // Build MIR graph
+ {
+ FunctionCompiler f(moduleEnv, d, func, locals, mir, masm.tryNotes());
+ if (!f.init()) {
+ return false;
+ }
+
+ if (!f.startBlock()) {
+ return false;
+ }
+
+ if (!EmitBodyExprs(f)) {
+ return false;
+ }
+
+ f.finish();
+ }
+
+ // Compile MIR graph
+ {
+ jit::SpewBeginWasmFunction(&mir, func.index);
+ jit::AutoSpewEndFunction spewEndFunction(&mir);
+
+ if (!OptimizeMIR(&mir)) {
+ return false;
+ }
+
+ LIRGraph* lir = GenerateLIR(&mir);
+ if (!lir) {
+ return false;
+ }
+
+ CodeGenerator codegen(&mir, lir, &masm);
+
+ BytecodeOffset prologueTrapOffset(func.lineOrBytecode);
+ FuncOffsets offsets;
+ ArgTypeVector args(funcType);
+ if (!codegen.generateWasm(CallIndirectId::forFunc(moduleEnv, func.index),
+ prologueTrapOffset, args, trapExitLayout,
+ trapExitLayoutNumWords, &offsets,
+ &code->stackMaps, &d)) {
+ return false;
+ }
+
+ if (!code->codeRanges.emplaceBack(func.index, func.lineOrBytecode,
+ offsets)) {
+ return false;
+ }
+ }
+
+ JitSpew(JitSpew_Codegen,
+ "# wasm::IonCompileFunctions: completed function index %d",
+ (int)func.index);
+ JitSpew(JitSpew_Codegen, "# ==");
+ JitSpew(JitSpew_Codegen,
+ "# ================================"
+ "==================================");
+ JitSpewCont(JitSpew_Codegen, "\n");
+ }
+
+ masm.finish();
+ if (masm.oom()) {
+ return false;
+ }
+
+ return code->swap(masm);
+}
+
+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
+}