using UniqueCompileInfo = UniquePtr<CompileInfo>; using UniqueCompileInfoVector = Vector<UniqueCompileInfo, 1, SystemAllocPolicy>;
using BlockVector = Vector<MBasicBlock*, 8, SystemAllocPolicy>; using DefVector = Vector<MDefinition*, 8, SystemAllocPolicy>; using ControlInstructionVector =
Vector<MControlInstruction*, 8, SystemAllocPolicy>;
// [SMDOC] WebAssembly Exception Handling in Ion // ======================================================= // // ## Throwing instructions // // Wasm exceptions can be thrown by either a throw instruction (local throw), // or by a wasm call. // // ## The "catching try control" // // We know we are in try-code if there is a surrounding ControlItem with // LabelKind::Try. The innermost such control is called the // "catching try control". // // ## Throws without a catching try control // // Such throws are implemented with an instance call that triggers the exception // unwinding runtime. The exception unwinding runtime will not return to the // function. // // ## "landing pad" and "pre-pad" blocks // // When an exception is thrown, the unwinder will search for the nearest // enclosing try block and redirect control flow to it. The code that executes // before any catch blocks is called the 'landing pad'. The 'landing pad' is // responsible to: // 1. Consume the pending exception state from // Instance::pendingException(Tag) // 2. Branch to the correct catch block, or else rethrow // // There is one landing pad for each try block. The immediate predecessors of // the landing pad are called 'pre-pad' blocks. There is one pre-pad block per // throwing instruction. // // ## Creating pre-pad blocks // // There are two possible sorts of pre-pad blocks, depending on whether we // are branching after a local throw instruction, or after a wasm call: // // - If we encounter a local throw, we create the exception and tag objects, // store them to Instance::pendingException(Tag), and then jump to the // landing pad. // // - If we encounter a wasm call, we construct a MWasmCallCatchable which is a // control instruction with either a branch to a fallthrough block or // to a pre-pad block. // // The pre-pad block for a wasm call is empty except for a jump to the // landing pad. It only exists to avoid critical edges which when split would // violate the invariants of MWasmCallCatchable. The pending exception state // is taken care of by the unwinder. // // Each pre-pad ends with a pending jump to the landing pad. The pending jumps // to the landing pad are tracked in `tryPadPatches`. These are called // "pad patches". // // ## Creating the landing pad // // When we exit try-code, we check if tryPadPatches has captured any control // instructions (pad patches). If not, we don't compile any catches and we mark // the rest as dead code. // // If there are pre-pad blocks, we join them to create a landing pad (or just // "pad"). The pad's last two slots are the caught exception, and the // exception's tag object. // // There are three different forms of try-catch/catch_all Wasm instructions, // which result in different form of landing pad. // // 1. A catchless try, so a Wasm instruction of the form "try ... end". // - In this case, we end the pad by rethrowing the caught exception. // // 2. A single catch_all after a try. // - If the first catch after a try is a catch_all, then there won't be // any more catches, but we need the exception and its tag object, in // case the code in a catch_all contains "rethrow" instructions. // - The Wasm instruction "rethrow", gets the exception and tag object to // rethrow from the last two slots of the landing pad which, due to // validation, is the l'th surrounding ControlItem. // - We immediately GoTo to a new block after the pad and pop both the // exception and tag object, as we don't need them anymore in this case. // // 3. Otherwise, there is one or more catch code blocks following. // - In this case, we construct the landing pad by creating a sequence // of compare and branch blocks that compare the pending exception tag // object to the tag object of the current tagged catch block. This is // done incrementally as we visit each tagged catch block in the bytecode // stream. At every step, we update the ControlItem's block to point to // the next block to be created in the landing pad sequence. The final // block will either be a rethrow, if there is no catch_all, or else a // jump to a catch_all block.
struct TryControl { // Branches to bind to the try's landing pad.
ControlInstructionVector landingPadPatches; // For `try_table`, the list of tagged catches and labels to branch to.
TryTableCatchVector catches; // The pending exception for the try's landing pad.
MDefinition* pendingException; // The pending exception's tag for the try's landing pad.
MDefinition* pendingExceptionTag; // Whether this try is in the body and should catch any thrown exception. bool inBody;
// Reset the try control for when it is cached in FunctionCompiler. void reset() {
landingPadPatches.clearAndFree();
catches.clearAndFree();
inBody = false;
}
}; using UniqueTryControl = UniquePtr<TryControl>; using VectorUniqueTryControl = Vector<UniqueTryControl, 2, SystemAllocPolicy>;
using ControlFlowPatchVector = Vector<ControlFlowPatch, 0, SystemAllocPolicy>;
struct PendingBlockTarget {
ControlFlowPatchVector patches;
BranchHint hint = BranchHint::Invalid;
};
using PendingBlockTargetVector =
Vector<PendingBlockTarget, 0, SystemAllocPolicy>;
// Inlined functions accumulate all returns to be bound to a caller function // after compilation is finished. struct PendingInlineReturn {
PendingInlineReturn(MGoto* jump, DefVector&& results)
: jump(jump), results(std::move(results)) {}
MGoto* jump;
DefVector results;
};
using PendingInlineReturnVector =
Vector<PendingInlineReturn, 1, SystemAllocPolicy>;
// CallCompileState describes a call that is being compiled. struct CallCompileState { // A generator object that is passed each argument as it is compiled.
ABIArgGenerator abi;
// Whether we pass FP values through GPRs or FPRs. bool hardFP = true;
// The ABI we are using for this call.
ABIKind abiKind;
// Accumulates the register arguments while compiling arguments.
MWasmCallBase::Args regArgs;
// Reserved argument for passing Instance* to builtin instance method calls.
ABIArg instanceArg;
// The stack area in which the callee will write stack return values, or // nullptr if no stack results.
MWasmStackResultArea* stackResultArea = nullptr;
// Indicates that the call is a return/tail call. bool returnCall = false;
// The landing pad patches for the nearest enclosing try-catch. This is // non-null iff the call is catchable.
ControlInstructionVector* tryLandingPadPatches = nullptr;
// The index of the try note for a catchable call.
uint32_t tryNoteIndex = UINT32_MAX;
// The block to take for fallthrough execution for a catchable call.
MBasicBlock* fallthroughBlock = nullptr;
// The block to take for exceptional execution for a catchable call.
MBasicBlock* prePadBlock = nullptr;
explicit CallCompileState(ABIKind abiKind) : abi(abiKind), abiKind(abiKind) { if (abiKind == ABIKind::System) { // The system ABI follows the hardFP setting on ARM32. #ifdefined(JS_CODEGEN_ARM)
hardFP = ARMFlags::UseHardFpABI();
abi.setUseHardFp(hardFP); #endif
} else { #ifdefined(JS_CODEGEN_ARM)
MOZ_ASSERT(hardFP, "The WASM ABI passes FP arguments in registers"); #endif
}
}
struct IonCompilePolicy { // We store SSA definitions in the value stack. using Value = MDefinition*; using ValueVector = DefVector;
// We store loop headers and then/else blocks in the control flow stack. // In the case of try-catch control blocks, we collect additional information // regarding the possible paths from throws and calls to a landing pad, as // well as information on the landing pad's handlers (its catches). using ControlItem = Control;
};
using IonOpIter = OpIter<IonCompilePolicy>;
// Statistics for inlining (at all depths) into the root function. struct InliningStats {
size_t inlinedDirectBytecodeSize = 0; // sum of sizes of inlinees
size_t inlinedDirectFunctions = 0; // number of inlinees
size_t inlinedCallRefBytecodeSize = 0; // sum of sizes of inlinees
size_t inlinedCallRefFunctions = 0; // number of inlinees bool largeFunctionBackoff = false; // did large function backoff happen?
};
// Encapsulates the generation of MIR for a wasm function and any functions // that become inlined into it. class RootCompiler { const CompilerEnvironment& compilerEnv_; const CodeMetadata& codeMeta_; const CodeTailMetadata* codeTailMeta_;
// The current loop depth we're generating inside of. This includes all // callee functions when we're generating an inlined function, and so it // lives here on the root compiler.
uint32_t loopDepth_;
// The current stack of bytecode offsets of the caller functions of the // function currently being inlined.
BytecodeOffsetVector inlinedCallerOffsets_;
InlinedCallerOffsetIndex inlinedCallerOffsetsIndex_;
// Compilation statistics for this function.
CompileStats funcStats_;
// Accumulated inlining statistics for this function.
InliningStats inliningStats_; // The remaining inlining budget, in terms of bytecode bytes. This may go // negative and so is signed.
int64_t localInliningBudget_;
// All jit::CompileInfo objects created during this compilation. This must // be kept alive for as long as the MIR graph is alive.
UniqueCompileInfoVector compileInfos_;
// Cache of TryControl to minimize heap allocations.
VectorUniqueTryControl tryControlCache_;
// Reference to masm.tryNotes()
wasm::TryNoteVector& tryNotes_;
// Reference to masm.inliningContext()
wasm::InliningContext& inliningContext_;
// Add a compile info for an inlined function. This keeps the inlined // function's compile info alive for the outermost function's // compilation.
[[nodiscard]] CompileInfo* startInlineCall(
uint32_t callerFuncIndex, BytecodeOffset callerOffset,
uint32_t calleeFuncIndex, uint32_t numLocals, size_t inlineeBytecodeSize,
InliningHeuristics::CallKind callKind); void finishInlineCall();
// Add a try note and return the index.
[[nodiscard]] bool addTryNote(uint32_t* tryNoteIndex) { if (!tryNotes_.append(wasm::TryNote())) { returnfalse;
}
*tryNoteIndex = tryNotes_.length() - 1; returntrue;
}
// Try to get a free TryControl from the cache, or allocate a new one.
[[nodiscard]] UniqueTryControl newTryControl() { if (tryControlCache_.empty()) { return UniqueTryControl(js_new<TryControl>());
}
UniqueTryControl tryControl = std::move(tryControlCache_.back());
tryControlCache_.popBack(); return tryControl;
}
// Release the TryControl to the cache. void freeTryControl(UniqueTryControl&& tryControl) { // Ensure that it's in a consistent state
tryControl->reset(); // Ignore any OOM, as we'll fail later
(void)tryControlCache_.append(std::move(tryControl));
}
};
// Encapsulates the generation of MIR for a single function in a wasm module. class FunctionCompiler { // The root function compiler we are being compiled within.
RootCompiler& rootCompiler_;
// The caller function compiler, if any, that we are being inlined into. // Note that `inliningDepth_` is zero for the first inlinee, one for the // second inlinee, etc. const FunctionCompiler* callerCompiler_; const uint32_t inliningDepth_;
// Information about this function's bytecode and parsing state
IonOpIter iter_;
uint32_t functionBodyOffset_; const FuncCompileInput& func_; const ValTypeVector& locals_;
size_t lastReadCallSite_;
size_t numCallRefs_;
size_t numAllocSites_;
// CompileInfo for compiling the MIR for this function. Allocated inside of // RootCompiler::compileInfos, and kept alive for the duration of the // total compilation. const jit::CompileInfo& info_;
// When generating a forward branch we haven't created the basic block that // the branch needs to target. We handle this by accumulating all the branch // instructions that want to target a block we have not yet created into // `pendingBlocks_` and then patching them in `bindBranches`. // // For performance reasons we only grow `pendingBlocks_` as needed, never // shrink it. So the length of the vector has no relation to the current // nesting depth of wasm blocks. We use `pendingBlockDepth_` to track the // current wasm block depth. We assert that all entries beyond the current // block depth are empty.
uint32_t pendingBlockDepth_;
PendingBlockTargetVector pendingBlocks_; // Control flow patches for exceptions that are caught without a landing // pad they can directly jump to. This happens when either: // (1) `delegate` targets the function body label. // (2) A `try` ends without any cases, and there is no enclosing `try`. // (3) There is no `try` in this function, but a caller function (when // inlining) has a `try`. // // These exceptions will be rethrown using `emitBodyRethrowPad`.
ControlInstructionVector bodyRethrowPadPatches_; // A vector of the returns in this function for use when we're being inlined // into another function.
PendingInlineReturnVector pendingInlineReturns_; // A block that all uncaught exceptions in this function will jump to. The // inline caller will link this to the nearest enclosing catch handler.
MBasicBlock* pendingInlineCatchBlock_;
// Instance pointer argument to the current function.
MWasmParameter* instancePointer_;
MWasmParameter* stackResultPointer_;
public: // Construct a FunctionCompiler for the root function of a compilation
FunctionCompiler(RootCompiler& rootCompiler, Decoder& decoder, const FuncCompileInput& func, const ValTypeVector& locals, const CompileInfo& compileInfo)
: rootCompiler_(rootCompiler),
callerCompiler_(nullptr),
inliningDepth_(0),
iter_(rootCompiler.codeMeta(), decoder, locals),
functionBodyOffset_(decoder.beginOffset()),
func_(func),
locals_(locals),
lastReadCallSite_(0),
numCallRefs_(0),
numAllocSites_(0),
info_(compileInfo),
curBlock_(nullptr),
maxStackArgBytes_(0),
pendingBlockDepth_(0),
pendingInlineCatchBlock_(nullptr),
instancePointer_(nullptr),
stackResultPointer_(nullptr) {}
// Construct a FunctionCompiler for an inlined callee of a compilation
FunctionCompiler(const FunctionCompiler* callerCompiler, Decoder& decoder, const FuncCompileInput& func, const ValTypeVector& locals, const CompileInfo& compileInfo)
: rootCompiler_(callerCompiler->rootCompiler_),
callerCompiler_(callerCompiler),
inliningDepth_(callerCompiler_->inliningDepth() + 1),
iter_(rootCompiler_.codeMeta(), decoder, locals),
functionBodyOffset_(decoder.beginOffset()),
func_(func),
locals_(locals),
lastReadCallSite_(0),
numCallRefs_(0),
numAllocSites_(0),
info_(compileInfo),
curBlock_(nullptr),
maxStackArgBytes_(0),
pendingBlockDepth_(0),
pendingInlineCatchBlock_(nullptr),
instancePointer_(callerCompiler_->instancePointer_),
stackResultPointer_(nullptr) {}
MWasmParameter* ins =
MWasmParameter::New(alloc(), *i, i.mirType(), argRefType);
curBlock_->add(ins); if (args.isSyntheticStackResultPointerArg(i.index())) {
MOZ_ASSERT(stackResultPointer_ == nullptr);
stackResultPointer_ = ins;
} else {
curBlock_->initSlot(info().localSlot(args.naturalIndex(i.index())),
ins);
} if (!mirGen().ensureBallast()) { returnfalse;
}
}
// 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()) { returnfalse;
}
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()) { returnfalse;
}
}
returntrue;
}
[[nodiscard]] bool initInline(const DefVector& argValues) { // "This is an inlined-callee FunctionCompiler"
MOZ_ASSERT(callerCompiler_);
// Prepare the entry block for MIR generation: if (!mirGen().ensureBallast()) { returnfalse;
} if (!newBlock(nullptr, &curBlock_)) { returnfalse;
}
MBasicBlock* pred = callerCompiler_->curBlock_;
pred->end(MGoto::New(alloc(), curBlock_)); if (!curBlock_->addPredecessorWithoutPhis(pred)) { returnfalse;
}
// Set up args slots to point to passed argument values const FuncType& type = funcType(); for (uint32_t argIndex = 0; argIndex < type.args().length(); argIndex++) {
curBlock_->initSlot(info().localSlot(argIndex), argValues[argIndex]);
}
// Set up a parameter that receives the hidden instance pointer argument.
instancePointer_ = callerCompiler_->instancePointer_;
// Initialize all local slots to zero value for (size_t i = type.args().length(); i < locals_.length(); i++) {
ValType slotValType = locals_[i]; #ifndef ENABLE_WASM_SIMD if (slotValType == ValType::V128) { return iter().fail("Ion has no SIMD support yet");
} #endif
MDefinition* zero = constantZeroOfValType(slotValType);
curBlock_->initSlot(info().localSlot(i), zero); if (!mirGen().ensureBallast()) { returnfalse;
}
}
// 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;
MDefinition* constantNullRef(MaybeRefType type) { if (inDeadCode()) { return nullptr;
} // MConstant has a lot of baggage so we don't use that here.
MWasmNullConstant* constant = MWasmNullConstant::New(alloc(), type);
curBlock_->add(constant); return constant;
}
// Produce a zero constant for the specified ValType.
MDefinition* constantZeroOfValType(ValType valType) { switch (valType.kind()) { case ValType::I32: return constantI32(0); case ValType::I64: return constantI64(int64_t(0)); #ifdef ENABLE_WASM_SIMD case ValType::V128: return constantV128(V128(0)); #endif case ValType::F32: return constantF32(0.0f); case ValType::F64: return constantF64(0.0); case ValType::Ref: return constantNullRef(MaybeRefType(valType.refType())); default:
MOZ_CRASH();
}
}
/***************************** Code generation (after local scope setup) */
void fence() { if (inDeadCode()) { return;
}
MWasmFence* ins = MWasmFence::New(alloc());
curBlock_->add(ins);
}
// 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;
}
// wasm can't fold x * 1.0 because of NaN with custom payloads. auto* ins =
MMul::NewWasm(alloc(), lhs, rhs, type, mode, mustPreserveNaN(type));
curBlock_->add(ins); return ins;
}
MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd) { if (inDeadCode()) { return nullptr;
} bool trapOnError = !codeMeta().isAsmJS(); if (!unsignd && type == MIRType::Int32) { // Enforce the signedness of the operation by coercing the operands // to signed. Otherwise, operands that "look" unsigned to Ion but // are not unsigned to Baldr (eg, unsigned right shifts) may lead to // the operation being executed unsigned. Applies to mod() as well. // // Do this for Int32 only since Int64 is not subject to the same // issues. // // Note the offsets passed to MWasmBuiltinTruncateToInt32 are wrong here, // but it doesn't matter: they're not codegen'd to calls since inputs // already are int32. auto* lhs2 = createTruncateToInt32(lhs);
curBlock_->add(lhs2);
lhs = lhs2; auto* rhs2 = createTruncateToInt32(rhs);
curBlock_->add(rhs2);
rhs = rhs2;
}
// For x86 and arm we implement i64 div via c++ builtin. // A call to c++ builtin requires instance pointer. #ifdefined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) if (type == MIRType::Int64) { auto* ins = MWasmBuiltinDivI64::New(alloc(), lhs, rhs, instancePointer_,
unsignd, trapOnError, trapSiteDesc());
curBlock_->add(ins); return ins;
} #endif
MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd) { if (inDeadCode()) { return nullptr;
} bool trapOnError = !codeMeta().isAsmJS(); if (!unsignd && type == MIRType::Int32) { // See block comment in div(). auto* lhs2 = createTruncateToInt32(lhs);
curBlock_->add(lhs2);
lhs = lhs2; auto* rhs2 = createTruncateToInt32(rhs);
curBlock_->add(rhs2);
rhs = rhs2;
}
// For x86 and arm we implement i64 mod via c++ builtin. // A call to c++ builtin requires instance pointer. #ifdefined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) if (type == MIRType::Int64) { auto* ins = MWasmBuiltinModI64::New(alloc(), lhs, rhs, instancePointer_,
unsignd, trapOnError, trapSiteDesc());
curBlock_->add(ins); return ins;
} #endif
// Should be handled separately because we call BuiltinThunk for this case // and so, need to add the dependency from instancePointer. if (type == MIRType::Double) { auto* ins = MWasmBuiltinModD::New(alloc(), lhs, rhs, instancePointer_,
type, bytecodeOffset());
curBlock_->add(ins); return ins;
}
#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.
/************************************************ Linear memory accesses */
// For detailed information about memory accesses, see "Linear memory // addresses and bounds checking" in WasmMemory.cpp.
private: // If the platform does not have a HeapReg, load the memory base from // instance.
MDefinition* maybeLoadMemoryBase(uint32_t memoryIndex) { #ifdef WASM_HAS_HEAPREG if (memoryIndex == 0) { return nullptr;
} #endif return memoryBase(memoryIndex);
}
public: // A value holding the memory base, whether that's HeapReg or some other // register.
MDefinition* memoryBase(uint32_t memoryIndex) {
AliasSet aliases = !codeMeta().memories[memoryIndex].canMovingGrow()
? AliasSet::None()
: AliasSet::Load(AliasSet::WasmHeapMeta); #ifdef WASM_HAS_HEAPREG if (memoryIndex == 0) {
MWasmHeapReg* base = MWasmHeapReg::New(alloc(), aliases);
curBlock_->add(base); return base;
} #endif
uint32_t offset =
memoryIndex == 0
? Instance::offsetOfMemory0Base()
: (Instance::offsetInData(
codeMeta().offsetOfMemoryInstanceData(memoryIndex) +
offsetof(MemoryInstanceData, base)));
MWasmLoadInstance* base = MWasmLoadInstance::New(
alloc(), instancePointer_, offset, MIRType::Pointer, aliases);
curBlock_->add(base); return base;
}
// Canonicalize floating point values for differential testing. if (Scalar::isFloatingType(accessType) &&
js::SupportDifferentialTesting()) { auto* canonicalize = MCanonicalizeNaN::New(alloc(), value);
curBlock_->add(canonicalize); return canonicalize;
} return value;
}
// Return true if the access requires an alignment check. If so, sets // *mustAdd to true if the offset must be added to the pointer before // checking. bool needAlignmentCheck(MemoryAccessDesc* access, MDefinition* base, bool* mustAdd) {
MOZ_ASSERT(!*mustAdd);
// asm.js accesses are always aligned and need no checks. if (codeMeta().isAsmJS() || !access->isAtomic()) { returnfalse;
}
// If the EA is known and aligned it will need no checks. if (base->isConstant()) { // We only care about the low bits, so overflow is OK, as is chopping off // the high bits of an i64 pointer.
uint32_t ptr = 0; if (isMem64(access->memoryIndex())) {
ptr = uint32_t(base->toConstant()->toInt64());
} else {
ptr = base->toConstant()->toInt32();
} if (((ptr + access->offset64()) & (access->byteSize() - 1)) == 0) { returnfalse;
}
}
// 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; returntrue;
}
// Fold a constant base into the offset and make the base 0, provided the // offset stays below the guard limit. The reason for folding the base into // the offset rather than vice versa is that a small offset can be ignored // by both explicit bounds checking and bounds check elimination. void foldConstantPointer(MemoryAccessDesc* access, MDefinition** base) {
PageSize pageSize = codeMeta().memories[access->memoryIndex()].pageSize(); if (pageSize != PageSize::Standard) { return;
}
// If the offset must be added because it is large or because the true EA must // be checked, compute the effective address, trapping on overflow. void maybeComputeEffectiveAddress(MemoryAccessDesc* access,
MDefinition** base, bool mustAddOffset) {
uint64_t offsetGuardLimit = GetMaxOffsetGuardLimit(
codeMeta().hugeMemoryEnabled(access->memoryIndex()),
codeMeta().memories[access->memoryIndex()].pageSize());
MWasmLoadInstance* needBoundsCheck(uint32_t memoryIndex) {
MOZ_RELEASE_ASSERT(codeMeta().memories[memoryIndex].pageSize() ==
PageSize::Standard); #ifdef JS_64BIT // For 32-bit base pointers: // // If the bounds check uses the full 64 bits of the bounds check limit, then // the base pointer must be zero-extended to 64 bits before checking and // wrapped back to 32-bits after Spectre masking. (And it's important that // the value we end up with has flowed through the Spectre mask.) // // If the memory's max size is known to be smaller than 64K pages exactly, // we can use a 32-bit check and avoid extension and wrapping. bool mem32LimitIs64Bits =
isMem32(memoryIndex) &&
!codeMeta().memories[memoryIndex].boundsCheckLimitIsAlways32Bits() &&
MaxMemoryBytes(codeMeta().memories[memoryIndex].addressType(),
codeMeta().memories[memoryIndex].pageSize()) >= 0x100000000; #else // On 32-bit platforms we have no more than 2GB memory and the limit for a // 32-bit base pointer is never a 64-bit value. bool mem32LimitIs64Bits = false; #endif return maybeLoadBoundsCheckLimit(memoryIndex,
mem32LimitIs64Bits || isMem64(memoryIndex)
? MIRType::Int64
: MIRType::Int32);
}
void performBoundsCheck(uint32_t memoryIndex, MDefinition** base,
MWasmLoadInstance* boundsCheckLimit) { // At the outset, actualBase could be the result of pretty much any integer // operation, or it could be the load of an integer constant. If its type // is i32, we may assume the value has a canonical representation for the // platform, see doc block in MacroAssembler.h.
MDefinition* actualBase = *base;
// Extend an i32 index value to perform a 64-bit bounds check if the memory // can be 4GB or larger. bool extendAndWrapIndex =
isMem32(memoryIndex) && boundsCheckLimit->type() == MIRType::Int64; if (extendAndWrapIndex) { auto* extended = MWasmExtendU32Index::New(alloc(), actualBase);
curBlock_->add(extended);
actualBase = extended;
}
// 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.
// Attempt to fold a constant base pointer into the offset so as to simplify // the addressing expression. This may update *base.
foldConstantPointer(access, base);
// Determine whether an alignment check is needed and whether the offset // must be checked too. bool mustAddOffsetForAlignmentCheck = false; bool alignmentCheck =
needAlignmentCheck(access, *base, &mustAddOffsetForAlignmentCheck);
// If bounds checking or alignment checking requires it, compute the // effective address: add the offset into the pointer and trap on overflow. // This may update *base.
maybeComputeEffectiveAddress(access, base, mustAddOffsetForAlignmentCheck);
// Emit the alignment check if necessary; it traps if it fails. if (alignmentCheck) {
curBlock_->add(MWasmAlignmentCheck::New(
alloc(), *base, access->byteSize(), trapSiteDesc()));
}
// Emit the bounds check if necessary; it traps if it fails. This may // update *base.
MWasmLoadInstance* boundsCheckLimit =
needBoundsCheck(access->memoryIndex()); if (boundsCheckLimit) {
performBoundsCheck(access->memoryIndex(), base, boundsCheckLimit);
}
#ifndef JS_64BIT if (isMem64(access->memoryIndex())) { // We must have had an explicit bounds check (or one was elided if it was // proved redundant), and on 32-bit systems the index will for sure fit in // 32 bits: the max memory is 2GB. So chop the index down to 32-bit to // simplify the back-end.
MOZ_ASSERT((*base)->type() == MIRType::Int64);
MOZ_ASSERT(!codeMeta().hugeMemoryEnabled(access->memoryIndex())); auto* chopped = MWasmWrapU32Index::New(alloc(), *base);
MOZ_ASSERT(chopped->type() == MIRType::Int32);
curBlock_->add(chopped);
*base = chopped;
} #endif
}
bool isSmallerAccessForI64(ValType result, const MemoryAccessDesc* access) { if (result == ValType::I64 && access->byteSize() <= 4) { // These smaller accesses should all be zero-extending.
MOZ_ASSERT(!isSignedIntType(access->type())); returntrue;
} returnfalse;
}
// Add the offset into the pointer to yield the EA; trap on overflow. Clears // the offset on the memory access as a result.
MDefinition* computeEffectiveAddress(MDefinition* base,
MemoryAccessDesc* access) { if (inDeadCode()) { return nullptr;
}
uint64_t offset = access->offset64(); if (offset == 0) { return base;
} auto* ins = MWasmAddOffset::New(alloc(), base, offset, trapSiteDesc());
curBlock_->add(ins);
access->clearOffset(); return ins;
}
// Generate better code (on x86) by loading as a double with an // operation that sign extends directly.
MemoryAccessDesc access(addr.memoryIndex, Scalar::Float64, addr.align,
addr.offset, trapSiteDesc(),
hugeMemoryEnabled(addr.memoryIndex));
access.setWidenSimd128Load(op); return load(addr.base, &access, ValType::V128);
}
MInstruction* load; if (global.isIndirect()) { // Pull a pointer to the value out of Instance::globalArea, then // load from that pointer. Note that the pointer is immutable // even though the value it points at may change, hence the use of // |true| for the first node's |isConst| value, irrespective of // the |isConst| formal parameter to this method. The latter // applies to the denoted value as a whole. auto* cellPtr = MWasmLoadInstanceDataField::New(
alloc(), MIRType::Pointer, global.offset(), /*isConst=*/true, instancePointer_);
curBlock_->add(cellPtr);
load = MWasmLoadGlobalCell::New(alloc(), global.type().toMIRType(),
cellPtr, global.type());
} else { // Pull the value directly out of Instance::globalArea.
load = MWasmLoadInstanceDataField::New(
alloc(), global.type().toMIRType(), global.offset(),
!global.isMutable(), instancePointer_,
global.type().toMaybeRefType());
}
curBlock_->add(load); return load;
}
if (global.isIndirect()) { // Pull a pointer to the value out of Instance::globalArea, then // store through that pointer. auto* valueAddr = MWasmLoadInstanceDataField::New(
alloc(), MIRType::Pointer, global.offset(), /*isConstant=*/true, instancePointer_);
curBlock_->add(valueAddr);
// Handle a store to a ref-typed field specially if (global.type().toMIRType() == MIRType::WasmAnyRef) { // Load the previous value for the post-write barrier
MOZ_ASSERT(v->type() == MIRType::WasmAnyRef); auto* prevValue = MWasmLoadGlobalCell::New(alloc(), MIRType::WasmAnyRef,
valueAddr, global.type());
curBlock_->add(prevValue);
// Store the new value auto* store =
MWasmStoreRef::New(alloc(), instancePointer_, valueAddr, /*valueOffset=*/0, v, AliasSet::WasmGlobalCell,
WasmPreBarrierKind::Normal);
curBlock_->add(store);
// Call the post-write barrier return postBarrierEdgePrecise(lineOrBytecode, valueAddr, prevValue);
}
auto* store = MWasmStoreGlobalCell::New(alloc(), v, valueAddr);
curBlock_->add(store); returntrue;
} // Or else store the value directly in Instance::globalArea.
// Handle a store to a ref-typed field specially if (global.type().toMIRType() == MIRType::WasmAnyRef) { // Compute the address of the ref-typed global auto* valueAddr = MWasmDerivedPointer::New(
alloc(), instancePointer_,
wasm::Instance::offsetInData(global.offset()));
curBlock_->add(valueAddr);
// Load the previous value for the post-write barrier
MOZ_ASSERT(v->type() == MIRType::WasmAnyRef); auto* prevValue = MWasmLoadGlobalCell::New(alloc(), MIRType::WasmAnyRef,
valueAddr, global.type());
curBlock_->add(prevValue);
// Store the new value auto* store =
MWasmStoreRef::New(alloc(), instancePointer_, valueAddr, /*valueOffset=*/0, v, AliasSet::WasmInstanceData,
WasmPreBarrierKind::Normal);
curBlock_->add(store);
// Call the post-write barrier return postBarrierEdgePrecise(lineOrBytecode, valueAddr, prevValue);
}
auto* store = MWasmStoreInstanceDataField::New(alloc(), global.offset(), v,
instancePointer_);
curBlock_->add(store); returntrue;
}
// Load the slot on the instance where the result of `ref.func` is cached. // This may be null if a function reference for this function has not been // asked for yet.
MDefinition* loadCachedRefFunc(uint32_t funcIndex) {
uint32_t exportedFuncIndex = codeMeta().findFuncExportIndex(funcIndex);
MWasmLoadInstanceDataField* refFunc = MWasmLoadInstanceDataField::New(
alloc(), MIRType::WasmAnyRef,
codeMeta().offsetOfFuncExportInstanceData(exportedFuncIndex) +
offsetof(FuncExportInstanceData, func), true, instancePointer_);
curBlock_->add(refFunc); return refFunc;
}
// Clamps a table address into i32 range. If the value is too large to fit in // an i32, it will be replaced with UINT32_MAX so that it will always fail a // 32-bit bounds check. Consider using an actual 64-bit bounds check if // possible.
MDefinition* clampTableAddressToI32(AddressType addressType,
MDefinition* address) { switch (addressType) { case AddressType::I32: return address; case AddressType::I64: auto* clamp = MWasmClampTable64Address::New(alloc(), address); if (!clamp) { return nullptr;
}
curBlock_->add(clamp); return clamp;
}
MOZ_CRASH("unknown address type");
}
// Load the table elements and load the element auto* elements = loadTableElements(tableIndex); auto* element = MWasmLoadTableElement::New(alloc(), elements, address32,
table.elemType());
curBlock_->add(element); return element;
}
// Load the table elements auto* elements = loadTableElements(tableIndex);
// Load the previous value auto* prevValue = MWasmLoadTableElement::New(alloc(), elements, address32,
table.elemType());
curBlock_->add(prevValue);
// Compute the value's location for the post barrier auto* loc = MWasmDerivedIndexPointer::New(alloc(), elements, address32,
ScalePointer);
curBlock_->add(loc);
// Store the new value auto* store = MWasmStoreRef::New(
alloc(), instancePointer_, loc, /*valueOffset=*/0, value,
AliasSet::WasmTableElement, WasmPreBarrierKind::Normal);
curBlock_->add(store);
// Perform the post barrier return postBarrierEdgePrecise(lineOrBytecode, loc, prevValue);
}
// Perform a post-write barrier to update the generational store buffer. This // version stores the entire containing object (e.g. a struct) rather than a // single edge.
[[nodiscard]] bool postBarrierWholeCell(uint32_t lineOrBytecode,
MDefinition* object,
MDefinition* newValue) { auto* barrier = MWasmPostWriteBarrierWholeCell::New(
alloc(), instancePointer_, object, newValue); if (!barrier) { returnfalse;
}
curBlock_->add(barrier); returntrue;
}
// Perform a post-write barrier to update the generational store buffer. This // version tracks a single tenured -> nursery edge, and will remove a previous // store buffer entry if it is no longer needed.
[[nodiscard]] bool postBarrierEdgePrecise(uint32_t lineOrBytecode,
MDefinition* valueAddr,
MDefinition* value) { return emitInstanceCall2(lineOrBytecode, SASigPostBarrierEdgePrecise,
valueAddr, value);
}
// Perform a post-write barrier to update the generational store buffer. This // version does not remove a previous store buffer entry if it is no longer // needed.
[[nodiscard]] bool postBarrierEdgeAtIndex(uint32_t lineOrBytecode,
MDefinition* object,
MDefinition* valueBase,
MDefinition* index, uint32_t scale,
MDefinition* newValue) { auto* barrier = MWasmPostWriteBarrierEdgeAtIndex::New(
alloc(), instancePointer_, object, valueBase, index, scale, newValue); if (!barrier) { returnfalse;
}
curBlock_->add(barrier); returntrue;
}
// 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).
// Should only pass an instance once. And it must be a non-GC pointer.
MOZ_ASSERT(callState->instanceArg == ABIArg());
MOZ_ASSERT(instanceType == MIRType::Pointer);
callState->instanceArg = callState->abi.next(MIRType::Pointer); returntrue;
}
// Do not call this directly. Call one of the passCallArg() variants instead.
[[nodiscard]] bool passCallArgWorker(MDefinition* argDef, MIRType type,
CallCompileState* callState) {
MOZ_ASSERT(argDef->type() == type);
// Calling a softFP function requires moving our floats into GPRs. if (!callState->hardFP &&
(type == MIRType::Double || type == MIRType::Float32)) {
MIRType softType =
(type == MIRType::Double) ? MIRType::Int64 : MIRType::Int32; auto* softDef = MReinterpretCast::New(alloc(), argDef, softType); if (!softDef) { returnfalse;
}
curBlock_->add(softDef);
argDef = softDef;
}
ABIArg arg = callState->abi.next(type); switch (arg.kind()) { #ifdef JS_CODEGEN_REGISTER_PAIR case ABIArg::GPR_PAIR: { auto mirLow =
MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ true);
curBlock_->add(mirLow); auto mirHigh =
MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ false);
curBlock_->add(mirHigh); return callState->regArgs.append(
MWasmCallBase::Arg(AnyRegister(arg.gpr64().low), mirLow)) &&
callState->regArgs.append(
MWasmCallBase::Arg(AnyRegister(arg.gpr64().high), mirHigh));
} #endif case ABIArg::GPR: case ABIArg::FPU: return callState->regArgs.append(MWasmCallBase::Arg(arg.reg(), argDef)); case ABIArg::Stack: { auto* mir =
MWasmStackArg::New(alloc(), arg.offsetFromArgBase(), argDef);
curBlock_->add(mir); returntrue;
} case ABIArg::Uninitialized:
MOZ_ASSERT_UNREACHABLE("Uninitialized ABIArg kind");
}
MOZ_CRASH("Unknown ABIArg kind.");
}
template <typename VecT>
[[nodiscard]] bool passCallArgs(const DefVector& argDefs, const VecT& types,
CallCompileState* callState) {
MOZ_ASSERT(argDefs.length() == types.length()); for (uint32_t i = 0; i < argDefs.length(); i++) {
MDefinition* def = argDefs[i];
ValType type = types[i]; if (!passCallArg(def, type, callState)) { returnfalse;
}
} returntrue;
}
// If the call returns results on the stack, prepare a stack area to receive // them, and pass the address of the stack area to the callee as an additional // argument.
[[nodiscard]] bool passStackResultAreaCallArg(const ResultType& resultType,
CallCompileState* callState) { if (inDeadCode()) { returntrue;
}
ABIResultIter iter(resultType); while (!iter.done() && iter.cur().inRegister()) {
iter.next();
} if (iter.done()) { // No stack results. returntrue;
}
// The builtin ABI only supports a single result value, so it doesn't // use stack results.
MOZ_ASSERT(callState->abiKind == ABIKind::Wasm);
[[nodiscard]] bool emitCallArgs(const FuncType& funcType, const DefVector& args,
CallCompileState* callState) { for (size_t i = 0, n = funcType.args().length(); i < n; ++i) { if (!mirGen().ensureBallast()) { returnfalse;
} if (!passCallArg(args[i], funcType.args()[i], callState)) { returnfalse;
}
}
ResultType resultType = ResultType::Vector(funcType.results()); if (!passStackResultAreaCallArg(resultType, callState)) { returnfalse;
}
return finishCallArgs(callState);
}
[[nodiscard]] bool collectBuiltinCallResult(MIRType type, MDefinition** result,
CallCompileState* callState) {
MInstruction* def; switch (type) { case MIRType::Int32:
def = MWasmRegisterResult::New(alloc(), MIRType::Int32, ReturnReg); break; case MIRType::Int64:
def = MWasmRegister64Result::New(alloc(), ReturnReg64); break; case MIRType::Float32: { if (callState->abiKind == ABIKind::System) {
def = MWasmSystemFloatRegisterResult::New(
alloc(), type, ReturnFloat32Reg, callState->hardFP);
} else {
def = MWasmFloatRegisterResult::New(alloc(), MIRType::Float32,
ReturnFloat32Reg);
} break;
} case MIRType::Double: { if (callState->abiKind == ABIKind::System) {
def = MWasmSystemFloatRegisterResult::New(
alloc(), type, ReturnDoubleReg, callState->hardFP);
} else {
def = MWasmFloatRegisterResult::New(alloc(), MIRType::Double,
ReturnDoubleReg);
} break;
} #ifdef ENABLE_WASM_SIMD case MIRType::Simd128:
MOZ_CRASH("SIMD128 not supported in builtin ABI"); #endif case MIRType::WasmAnyRef:
def = MWasmRegisterResult::New(alloc(), MIRType::WasmAnyRef, ReturnReg); break; case MIRType::None:
MOZ_ASSERT(result == nullptr, "Not expecting any results created"); returntrue; default:
MOZ_CRASH("unexpected MIRType result for builtin call");
}
if (!def) { returnfalse;
}
curBlock_->add(def);
*result = def;
returntrue;
}
[[nodiscard]] bool collectWasmCallResults(const ResultType& type,
CallCompileState* callState, DefVector* results) { // This function uses wasm::ABIResultIter which does not handle the system // ABI. Use collectBuiltinCallResult instead for builtin calls.
MOZ_ASSERT(callState->abiKind == ABIKind::Wasm);
MOZ_ASSERT(callState->hardFP);
if (!results->reserve(type.length())) { returnfalse;
}
// 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()) { returnfalse;
} const ABIResult& result = iter.cur();
MInstruction* def; if (result.inRegister()) { switch (result.type().kind()) { case wasm::ValType::I32:
def =
MWasmRegisterResult::New(alloc(), MIRType::Int32, result.gpr()); break; case wasm::ValType::I64:
def = MWasmRegister64Result::New(alloc(), result.gpr64()); break; case wasm::ValType::F32:
def = MWasmFloatRegisterResult::New(alloc(), MIRType::Float32,
result.fpr()); break; case wasm::ValType::F64:
def = MWasmFloatRegisterResult::New(alloc(), MIRType::Double,
result.fpr()); break; case wasm::ValType::Ref:
def = MWasmRegisterResult::New(alloc(), MIRType::WasmAnyRef,
result.gpr(),
result.type().toMaybeRefType()); break; case wasm::ValType::V128: #ifdef ENABLE_WASM_SIMD
def = MWasmFloatRegisterResult::New(alloc(), MIRType::Simd128,
result.fpr()); #else returnthis->iter().fail("Ion has no SIMD support yet"); #endif
}
} else {
MOZ_ASSERT(callState->stackResultArea);
MOZ_ASSERT(stackResultCount);
uint32_t idx = --stackResultCount;
def = MWasmStackResult::New(alloc(), callState->stackResultArea, idx);
}
if (!def) { returnfalse;
}
curBlock_->add(def);
results->infallibleAppend(def);
}
MInstruction* ins; if (callState->isCatchable()) {
ins = MWasmCallCatchable::New(
alloc(), desc, callee, callState->regArgs,
StackArgAreaSizeUnaligned(argTypes, callState->abiKind),
callState->tryNoteIndex, callState->fallthroughBlock,
callState->prePadBlock, addressOrRef);
} else {
ins = MWasmCallUncatchable::New(
alloc(), desc, callee, callState->regArgs,
StackArgAreaSizeUnaligned(argTypes, callState->abiKind),
addressOrRef);
} if (!ins) { returnfalse;
}
curBlock_->add(ins);
return finishCatchableCall(callState);
}
[[nodiscard]]
CallRefHint auditInlineableCallees(InliningHeuristics::CallKind kind,
CallRefHint hints) { // Takes candidates for inlining as provided in `hints`, and returns a // subset (or all) of them for which inlining is approved. To indicate // that they are all disallowed, return an empty CallRefHint.
// We only support inlining when lazy tiering. This is currently a // requirement because we need the full module bytecode and function // definition ranges, which are not available in other modes. if (compilerEnv().mode() != CompileMode::LazyTiering) { return CallRefHint();
}
// We don't support asm.js and inlining. asm.js also doesn't support // baseline, which is required for lazy tiering, so we should never get // here. The biggest complication for asm.js is getting correct stack // traces with inlining.
MOZ_ASSERT(!codeMeta().isAsmJS());
// If we were given no candidates, give up now. if (hints.empty()) { return CallRefHint();
}
// We can't inline if we've exceeded our per-root-function inlining // budget. // // This logic will cause `availableBudget` to be driven slightly negative // if a budget overshoot happens, so we will have performed slightly more // inlining than allowed by the initial setting of `availableBudget`. The // size of this overshoot is however very limited -- it can't exceed the // size of three function bodies that are inlined (3 because that's what // CallRefHint can hold). And the max size of an inlineable function body // is limited by InliningHeuristics::isSmallEnoughToInline. if (rootCompiler_.inliningBudget() < 0) { return CallRefHint();
}
// Check each candidate in turn, and add all acceptable ones to `filtered`. // It is important that `filtered` retains the same ordering as `hints`.
CallRefHint filtered; for (uint32_t i = 0; i < hints.length(); i++) {
uint32_t funcIndex = hints.get(i);
// We can't inline an imported function. if (codeMeta().funcIsImport(funcIndex)) { continue;
}
// We do not support inlining a callee which uses tail calls
FeatureUsage funcFeatureUsage =
codeTailMeta()->funcDefFeatureUsage(funcIndex); if (funcFeatureUsage & FeatureUsage::ReturnCall) { continue;
}
// Ask the heuristics system if we're allowed to inline a function of // this size and kind at the current inlining depth.
uint32_t inlineeBodySize = codeTailMeta()->funcDefRange(funcIndex).size();
uint32_t rootFunctionBodySize = rootCompiler_.func().bytecodeSize(); bool largeFunctionBackoff; bool smallEnough = InliningHeuristics::isSmallEnoughToInline(
kind, inliningDepth(), inlineeBodySize, rootFunctionBodySize,
&largeFunctionBackoff); if (largeFunctionBackoff) {
rootCompiler_.noteLargeFunctionBackoffWasApplied();
} if (!smallEnough) { continue;
}
filtered.append(funcIndex);
}
// Whatever ends up in `filtered` is approved for inlining. return filtered;
}
// Add the observed features from the inlined function to this function
iter_.addFeatureUsage(calleeCompiler.featureUsage());
// Create a block, if needed, to handle exceptions from the callee function if (calleeCatchBlock) {
ControlInstructionVector* tryLandingPadPatches; bool inTryCode = inTryBlock(&tryLandingPadPatches);
// The callee compiler should never create a catch block unless we have // a landing pad for it
MOZ_RELEASE_ASSERT(inTryCode);
// Create a block in our function to jump to the nearest try block. We // cannot just use the callee's catch block for this, as the slots on it // are set up for all the locals from that function. We need to create a // new block in our function with the slots for this function, that then // does the jump to the landing pad. Ion should be able to optimize this // away using jump threading.
MBasicBlock* callerCatchBlock = nullptr; if (!newBlock(nullptr, &callerCatchBlock)) { returnfalse;
}
// Our catch block inherits all of the locals state from immediately // before the inlined call
callerCatchBlock->inheritSlots(lastBlockBeforeCall);
// The callee catch block jumps to our catch block
calleeCatchBlock->end(MGoto::New(alloc(), callerCatchBlock));
// Our catch block has the callee rethrow block as a predecessor, but // ignores all phi's, because we use our own locals state. if (!callerCatchBlock->addPredecessorWithoutPhis(calleeCatchBlock)) { returnfalse;
}
// Our catch block ends with a patch to jump to the enclosing try block.
MBasicBlock* prevBlock = curBlock_;
curBlock_ = callerCatchBlock; if (!endWithPadPatch(tryLandingPadPatches)) { returnfalse;
}
curBlock_ = prevBlock;
}
// If there were no returns, then we are now in dead code if (calleeReturns.empty()) {
curBlock_ = nullptr; returntrue;
}
// Create a block to join all of the returns from the inlined function
MBasicBlock* joinAfterCall = nullptr; if (!newBlock(nullptr, &joinAfterCall)) { returnfalse;
}
// The join block inherits all of the locals state from immediately before // the inlined call
joinAfterCall->inheritSlots(lastBlockBeforeCall);
// The join block has a phi node for every result of the inlined function // type. Each phi node has an operand for each of the returns of the // inlined function. for (uint32_t i = 0; i < calleeFuncType.results().length(); i++) {
MPhi* phi = MPhi::New(alloc(), calleeFuncType.results()[i].toMIRType()); if (!phi || !phi->reserveLength(calleeReturns.length())) { returnfalse;
}
joinAfterCall->addPhi(phi); if (!results->append(phi)) { returnfalse;
}
}
// Bind every return from the inlined function to go to the join block, and // add the results for the return to the phi nodes. for (size_t i = 0; i < calleeReturns.length(); i++) { const PendingInlineReturn& calleeReturn = calleeReturns[i];
// Setup the predecessor and successor relationship
MBasicBlock* pred = calleeReturn.jump->block(); if (!joinAfterCall->addPredecessorWithoutPhis(pred)) { returnfalse;
}
calleeReturn.jump->replaceSuccessor(MGoto::TargetIndex, joinAfterCall);
// For each result in this return, add it to the corresponding phi node for (uint32_t resultIndex = 0;
resultIndex < calleeFuncType.results().length(); resultIndex++) {
MDefinition* result = (*results)[resultIndex];
((MPhi*)(result))->addInput(calleeReturn.results[resultIndex]);
}
}
// Continue MIR generation starting in the join block
curBlock_ = joinAfterCall;
// 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. `passInstanceCallArg`, `passCallArg`, `finishCall` and // `instanceCall` all do nothing in dead code, so it's valid // to exit here. if (inDeadCode()) { if (result) {
*result = nullptr;
} returntrue;
}
// 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;
} returnfalse;
}
}
// Finally, construct the call.
CallCompileState callState(ABIForBuiltin(callee.identity)); if (!passInstanceCallArg(callee.argTypes[0], &callState)) { returnfalse;
} for (size_t i = 0; i < numArgs; i++) { if (!passCallArg(args[i], callee.argTypes[i + 1], &callState)) { returnfalse;
}
} if (!finishCallArgs(&callState)) { returnfalse;
} return instanceCall(&callState, callee, lineOrBytecode, result);
}
// It's almost possible to use FunctionCompiler::emitInstanceCallN here. // Unfortunately not currently possible though, since ::emitInstanceCallN // expects an array of arguments along with a size, and that's not what is // available here. It would be possible if we were prepared to copy // `builtinModuleFunc->params` into a fixed-sized (16 element?) array, add // `memoryBase`, and make the call. const SymbolicAddressSignature& callee = *builtinModuleFunc.sig();
[[nodiscard]] bool returnValues(DefVector&& values) { if (inDeadCode()) { returntrue;
}
// If we're inlined into another function, we must accumulate the returns // so that they can be patched into the caller function. if (isInlined()) {
MGoto* jump = MGoto::New(alloc()); if (!jump) { returnfalse;
}
curBlock_->end(jump);
curBlock_ = nullptr; return pendingInlineReturns_.emplaceBack(
PendingInlineReturn(jump, std::move(values)));
}
if (values.empty()) {
curBlock_->end(MWasmReturnVoid::New(alloc(), instancePointer_));
} else {
ResultType resultType = ResultType::Vector(funcType().results());
ABIResultIter iter(resultType); // Switch to iterate in FIFO order instead of the default LIFO. while (!iter.done()) {
iter.next();
}
iter.switchToPrev(); for (uint32_t i = 0; !iter.done(); iter.prev(), i++) { if (!mirGen().ensureBallast()) { returnfalse;
} const ABIResult& result = iter.cur(); if (result.onStack()) {
MOZ_ASSERT(iter.remaining() > 1); auto* store = MWasmStoreStackResult::New(
alloc(), stackResultPointer_, result.stackOffset(), values[i]);
curBlock_->add(store);
} else {
MOZ_ASSERT(iter.remaining() == 1);
MOZ_ASSERT(i + 1 == values.length());
curBlock_->end(
MWasmReturn::New(alloc(), values[i], instancePointer_));
}
}
}
curBlock_ = nullptr; returntrue;
}
void unreachableTrap() { if (inDeadCode()) { return;
}
MBasicBlock* join; if (!goToNewBlock(blocks[0], &join)) { returnfalse;
} for (size_t i = 1; i < numJoinPreds; ++i) { if (!goToExistingBlock(blocks[i], join)) { returnfalse;
}
}
DefVector loopParams; if (!iter().getResults(paramCount, &loopParams)) { returnfalse;
}
// Eagerly create a phi for all loop params. setLoopBackedge will remove // any that were not necessary. for (size_t i = 0; i < paramCount; i++) {
MPhi* phi = MPhi::New(alloc(), loopParams[i]->type()); if (!phi) { returnfalse;
} if (!phi->reserveLength(2)) { returnfalse;
}
(*loopHeader)->addPhi(phi);
phi->addInput(loopParams[i]);
loopParams[i] = phi;
}
iter().setResults(paramCount, loopParams);
// Entering a loop will eagerly create a phi node for all locals and loop // params. Now that we've closed the loop we can check which phi nodes // were actually needed by checking if the SSA definition flowing into the // loop header (operand 0) is different than the SSA definition coming from // the loop backedge (operand 1). If they are the same definition, the phi // is redundant and can be removed. // // To do this we mark all redundant phis as 'unused', then remove the phi's // from places in ourself the phis may have flowed into, then replace all // uses of the phi's in the MIR graph with the original SSA definition. for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd();
phi++) {
MOZ_ASSERT(phi->numOperands() == 2); if (phi->getOperand(0) == phi->getOperand(1)) {
phi->setUnused();
}
}
// Fix up phis stored in the slots Vector of pending blocks. for (PendingBlockTarget& pendingBlockTarget : pendingBlocks_) { for (ControlFlowPatch& p : pendingBlockTarget.patches) {
MBasicBlock* block = p.ins->block(); if (block->loopDepth() >= loopEntry->loopDepth()) {
fixupRedundantPhis(block);
}
}
}
// The loop body, if any, might be referencing recycled phis too. if (loopBody) {
fixupRedundantPhis(loopBody);
}
// Pending jumps to an enclosing try-catch may reference the recycled phis. // We have to search above all enclosing try blocks, as a delegate may move // patches around. for (uint32_t depth = 0; depth < iter().controlStackDepth(); depth++) {
LabelKind kind = iter().controlKind(depth); if (kind != LabelKind::Try && kind != LabelKind::TryTable &&
kind != LabelKind::Body) { continue;
}
Control& control = iter().controlItem(depth); if (!control.tryControl) { continue;
} for (MControlInstruction* patch : control.tryControl->landingPadPatches) {
MBasicBlock* block = patch->block(); if (block->loopDepth() >= loopEntry->loopDepth()) {
fixupRedundantPhis(block);
}
}
} for (MControlInstruction* patch : bodyRethrowPadPatches_) {
MBasicBlock* block = patch->block(); if (block->loopDepth() >= loopEntry->loopDepth()) {
fixupRedundantPhis(block);
}
}
// If we're inlined into another function we are accumulating return values // in a vector, search through the results to see if any refer to a // redundant phi. for (PendingInlineReturn& pendingReturn : pendingInlineReturns_) { for (uint32_t resultIndex = 0;
resultIndex < pendingReturn.results.length(); resultIndex++) {
MDefinition** pendingResult = &pendingReturn.results[resultIndex]; if ((*pendingResult)->isUnused()) {
*pendingResult = (*pendingResult)->toPhi()->getOperand(0);
}
}
}
// Discard redundant phis and add to the free list. for (MPhiIterator phi = loopEntry->phisBegin();
phi != loopEntry->phisEnd();) {
MPhi* entryDef = *phi++; if (!entryDef->isUnused()) { continue;
}
// 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)) { returnfalse;
}
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 the loop depth still at the inner loop body, correct it. if (curBlock_ && curBlock_->loopDepth() != rootCompiler_.loopDepth()) {
MBasicBlock* out; if (!goToNewBlock(curBlock_, &out)) { returnfalse;
}
curBlock_ = out;
}
// Find where we are delegating the pad patches to.
ControlInstructionVector* targetPatches; if (!inTryBlockFrom(relativeDepth, &targetPatches)) {
MOZ_ASSERT(relativeDepth <= pendingBlockDepth_ - 1);
targetPatches = &bodyRethrowPadPatches_;
}
// Append the delegate's pad patches to the target's. for (MControlInstruction* ins : patches) { if (!targetPatches->emplaceBack(ins)) { returnfalse;
}
} returntrue;
}
// Allocate a try note if (!rootCompiler_.addTryNote(&callState->tryNoteIndex)) { returnfalse;
}
// Allocate blocks for fallthrough and exceptions return newBlock(curBlock_, &callState->fallthroughBlock) &&
newBlock(curBlock_, &callState->prePadBlock);
}
[[nodiscard]] bool finishCatchableCall(CallCompileState* callState) { if (!callState->tryLandingPadPatches) { returntrue;
}
// Switch to the prePadBlock
MBasicBlock* callBlock = curBlock_;
curBlock_ = callState->prePadBlock;
// Mark this as the landing pad for the call
curBlock_->add(MWasmCallLandingPrePad::New(alloc(), callBlock,
callState->tryNoteIndex));
// End with a pending jump to the landing pad if (!endWithPadPatch(callState->tryLandingPadPatches)) { returnfalse;
}
// Compilation continues in the fallthroughBlock.
curBlock_ = callState->fallthroughBlock; returntrue;
}
// Create a landing pad for a try block. This is also used for the implicit // rethrow landing pad used for delegate instructions that target the // outermost label.
[[nodiscard]] bool createTryLandingPad(ControlInstructionVector& landingPadPatches,
MBasicBlock** landingPad) {
MOZ_ASSERT(!landingPadPatches.empty());
// Bind the branches from exception throwing code to a new landing pad // block. This is done similarly to what is done in bindBranches.
MControlInstruction* ins = landingPadPatches[0];
MBasicBlock* pred = ins->block(); if (!newBlock(pred, landingPad)) { returnfalse;
}
ins->replaceSuccessor(MGoto::TargetIndex, *landingPad); for (size_t i = 1; i < landingPadPatches.length(); i++) {
ins = landingPadPatches[i];
pred = ins->block(); if (!(*landingPad)->addPredecessor(alloc(), pred)) { returnfalse;
}
ins->replaceSuccessor(MGoto::TargetIndex, *landingPad);
}
// Clear the now bound pad patches.
landingPadPatches.clear(); returntrue;
}
[[nodiscard]] bool createTryTableLandingPad(TryControl* tryControl) { // If there were no patches, then there were no throwing instructions and // we don't need to do anything. if (tryControl->landingPadPatches.empty()) { returntrue;
}
// Create the landing pad block and bind all the throwing instructions
MBasicBlock* landingPad; if (!createTryLandingPad(tryControl->landingPadPatches, &landingPad)) { returnfalse;
}
// Get the pending exception from the instance
MDefinition* pendingException;
MDefinition* pendingExceptionTag; if (!consumePendingException(&landingPad, &pendingException,
&pendingExceptionTag)) { returnfalse;
}
bool hadCatchAll = false; for (const TryTableCatch& tryTableCatch : tryControl->catches) { // Handle a catch_all by jumping to the target block if (tryTableCatch.tagIndex == CatchAllIndex) { // Capture the exnref value if we need to
DefVector values; if (tryTableCatch.captureExnRef && !values.append(pendingException)) { returnfalse;
}
// Branch to the catch_all code if (!br(tryTableCatch.labelRelativeDepth, values)) { returnfalse;
}
// Break from the loop and skip the implicit rethrow that's needed // if we didn't have a catch_all
hadCatchAll = true; break;
}
// Handle a tagged catch by doing a compare and branch on the tag index, // jumping to a catch block if they match, or else to a fallthrough block // to continue the landing pad.
MBasicBlock* catchBlock = nullptr;
MBasicBlock* fallthroughBlock = nullptr; if (!newBlock(curBlock_, &catchBlock) ||
!newBlock(curBlock_, &fallthroughBlock)) { returnfalse;
}
// Branch to the catch block if the exception's tag matches this catch // block's tag.
MDefinition* catchTag = loadTag(tryTableCatch.tagIndex);
MDefinition* matchesCatchTag =
compare(pendingExceptionTag, catchTag, JSOp::Eq,
MCompare::Compare_WasmAnyRef);
curBlock_->end(
MTest::New(alloc(), matchesCatchTag, catchBlock, fallthroughBlock));
// Set up the catch block by extracting the values from the exception // object.
curBlock_ = catchBlock;
// Extract the exception values for the catch block
DefVector values; if (!loadExceptionValues(pendingException, tryTableCatch.tagIndex,
&values)) { returnfalse;
} if (tryTableCatch.captureExnRef && !values.append(pendingException)) { returnfalse;
}
if (!br(tryTableCatch.labelRelativeDepth, values)) { returnfalse;
}
curBlock_ = fallthroughBlock;
}
// If there was no catch_all, we must rethrow this exception. if (!hadCatchAll) { if (!throwFrom(pendingException, pendingExceptionTag)) { returnfalse;
}
}
curBlock_ = originalBlock; returntrue;
}
// Consume the pending exception state from instance. This will clear out the // previous value.
[[nodiscard]] bool consumePendingException(MBasicBlock** landingPad,
MDefinition** pendingException,
MDefinition** pendingExceptionTag) {
MBasicBlock* prevBlock = curBlock_;
curBlock_ = *landingPad;
// Load the pending exception and tag
loadPendingExceptionState(pendingException, pendingExceptionTag);
// Clear the pending exception and tag auto* null = constantNullRef(MaybeRefType()); if (!setPendingExceptionState(null, null)) { returnfalse;
}
// The landing pad may have changed from loading and clearing the pending // exception state.
*landingPad = curBlock_;
[[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()) { returntrue;
}
// 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)) { returnfalse;
}
// Finish the current block with the control flow patch instruction.
curBlock_->end(jump); returntrue;
}
// Finish the previous block (either a try or catch block) and then setup a // new catch block.
[[nodiscard]] bool switchToCatch(Control& control, LabelKind fromKind,
uint32_t tagIndex) { // Mark this control node as being no longer in the body of the try
control.tryControl->inBody = false;
// If there is no control block, then either: // - the entry of the try block is dead code, or // - there is no landing pad for the try-catch. // In either case, any catch will be dead code. if (!control.block) {
MOZ_ASSERT(inDeadCode()); returntrue;
}
// Join the previous try or catch block with a patch to the future join of // the whole try-catch block. if (!joinTryOrCatchBlock(control)) { returnfalse;
}
// If we are switching from the try block, create the landing pad. This is // guaranteed to happen once and only once before processing catch blocks. if (fromKind == LabelKind::Try) { if (!control.tryControl->landingPadPatches.empty()) { // Create the landing pad block and bind all the throwing instructions
MBasicBlock* padBlock = nullptr; if (!createTryLandingPad(control.tryControl->landingPadPatches,
&padBlock)) { returnfalse;
}
// Store the pending exception and tag on the control item for future // use in catch handlers. if (!consumePendingException(
&padBlock, &control.tryControl->pendingException,
&control.tryControl->pendingExceptionTag)) { returnfalse;
}
// Set the control block for this try-catch to the landing pad.
control.block = padBlock;
} else {
control.block = nullptr;
}
}
// If there is no landing pad, then this and following catches are dead // code. if (!control.block) {
curBlock_ = nullptr; returntrue;
}
// Switch to the landing pad.
curBlock_ = control.block;
// We should have a pending exception and tag if we were able to create a // landing pad.
MOZ_ASSERT(control.tryControl->pendingException);
MOZ_ASSERT(control.tryControl->pendingExceptionTag);
// Handle a catch_all by immediately jumping to a new block. We require a // new block (as opposed to just emitting the catch_all code in the current // block) because rethrow requires the exception/tag to be present in the // landing pad's slots, while the catch_all block must not have the // exception/tag in slots. if (tagIndex == CatchAllIndex) {
MBasicBlock* catchAllBlock = nullptr; if (!goToNewBlock(curBlock_, &catchAllBlock)) { returnfalse;
} // Compilation will continue in the catch_all block.
curBlock_ = catchAllBlock; returntrue;
}
// 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)) { returnfalse;
}
// Branch to the catch block if the exception's tag matches this catch // block's tag.
MDefinition* catchTag = loadTag(tagIndex);
MDefinition* matchesCatchTag =
compare(control.tryControl->pendingExceptionTag, catchTag, JSOp::Eq,
MCompare::Compare_WasmAnyRef);
curBlock_->end(
MTest::New(alloc(), matchesCatchTag, catchBlock, fallthroughBlock));
// The landing pad will continue in the fallthrough block
control.block = fallthroughBlock;
// Set up the catch block by extracting the values from the exception // object.
curBlock_ = catchBlock;
// Extract the exception values for the catch block
DefVector values; if (!loadExceptionValues(control.tryControl->pendingException, tagIndex,
&values)) { returnfalse;
}
iter().setResults(values.length(), values); returntrue;
}
// Get the data pointer from the exception object auto* data = MWasmLoadField::New(
alloc(), exception, nullptr, WasmExceptionObject::offsetOfData(),
mozilla::Nothing(), MIRType::Pointer, MWideningOp::None,
AliasSet::Load(AliasSet::Any)); if (!data) { returnfalse;
}
curBlock_->add(data);
// Presize the values vector to the number of params if (!values->reserve(params.length())) { returnfalse;
}
// Load each value from the data pointer for (size_t i = 0; i < params.length(); i++) { if (!mirGen().ensureBallast()) { returnfalse;
} auto* load =
MWasmLoadField::New(alloc(), data, exception, offsets[i],
mozilla::Nothing(), params[i].toMIRType(),
MWideningOp::None, AliasSet::Load(AliasSet::Any),
mozilla::Nothing(), params[i].toMaybeRefType()); if (!load || !values->append(load)) { returnfalse;
}
curBlock_->add(load);
} returntrue;
}
[[nodiscard]] bool finishTryCatch(LabelKind kind, Control& control,
DefVector* defs) { switch (kind) { case LabelKind::Try: { // This is a catchless try, we must delegate all throwing instructions // to the nearest enclosing try block if one exists, or else to the // body block which will handle it in emitBodyRethrowPad. We // specify a relativeDepth of '1' to delegate outside of the still // active try block.
uint32_t relativeDepth = 1; if (!delegatePadPatches(control.tryControl->landingPadPatches,
relativeDepth)) { returnfalse;
} break;
} case LabelKind::Catch: {
MOZ_ASSERT(!control.tryControl->inBody); // This is a try without a catch_all, we must have a rethrow at the end // of the landing pad (if any).
MBasicBlock* padBlock = control.block; if (padBlock) {
MBasicBlock* prevBlock = curBlock_;
curBlock_ = padBlock; if (!throwFrom(control.tryControl->pendingException,
control.tryControl->pendingExceptionTag)) { returnfalse;
}
curBlock_ = prevBlock;
} break;
} case LabelKind::CatchAll: {
MOZ_ASSERT(!control.tryControl->inBody); // This is a try with a catch_all, and requires no special handling. break;
} default:
MOZ_CRASH();
}
// Finish the block, joining the try and catch blocks return finishBlock(defs);
}
[[nodiscard]] bool finishTryTable(Control& control, DefVector* defs) { // Mark this control as no longer in the body of the try
control.tryControl->inBody = false; // Create a landing pad for all of the catches if (!createTryTableLandingPad(control.tryControl.get())) { returnfalse;
} // Finish the block, joining the try and catch blocks return finishBlock(defs);
}
[[nodiscard]] bool emitBodyRethrowPad(Control& control) { // If there are no throwing instructions pending, we don't need to do // anything if (bodyRethrowPadPatches_.empty()) { returntrue;
}
// Create a landing pad for any throwing instructions
MBasicBlock* padBlock; if (!createTryLandingPad(bodyRethrowPadPatches_, &padBlock)) { returnfalse;
}
// If we're inlined into another function, we save the landing pad to be // linked later directly to our caller's landing pad. See // `finishedInlinedCallDirect`. if (callerCompiler_ && callerCompiler_->inTryCode()) {
pendingInlineCatchBlock_ = padBlock; returntrue;
}
// Otherwise we need to grab the pending exception and rethrow it.
MDefinition* pendingException;
MDefinition* pendingExceptionTag; if (!consumePendingException(&padBlock, &pendingException,
&pendingExceptionTag)) { returnfalse;
}
// Switch to the landing pad and rethrow the exception
MBasicBlock* prevBlock = curBlock_;
curBlock_ = padBlock; if (!throwFrom(pendingException, pendingExceptionTag)) { returnfalse;
}
curBlock_ = prevBlock;
// Load the tag
MDefinition* tag = loadTag(tagIndex); if (!tag) { returnfalse;
}
// Allocate an exception object
MDefinition* exception; if (!emitNewException(tag, &exception)) { returnfalse;
}
// Load the data pointer from the object auto* data = MWasmLoadField::New(
alloc(), exception, nullptr, WasmExceptionObject::offsetOfData(),
mozilla::Nothing(), MIRType::Pointer, MWideningOp::None,
AliasSet::Load(AliasSet::Any)); if (!data) { returnfalse;
}
curBlock_->add(data);
// Store the params into the data pointer
SharedTagType tagType = codeMeta().tags[tagIndex].type; for (size_t i = 0; i < tagType->exceptionArgOffsets().length(); i++) { if (!mirGen().ensureBallast()) { returnfalse;
}
ValType type = tagType->argTypes()[i];
uint32_t offset = tagType->exceptionArgOffsets()[i];
if (!type.isRefRepr()) { auto* store = MWasmStoreField::New(
alloc(), data, exception, offset, mozilla::Nothing(), argValues[i],
MNarrowingOp::None, AliasSet::Store(AliasSet::Any)); if (!store) { returnfalse;
}
curBlock_->add(store); continue;
}
// Store the new value auto* store = MWasmStoreFieldRef::New(
alloc(), instancePointer_, data, exception, offset,
mozilla::Nothing(), argValues[i], AliasSet::Store(AliasSet::Any),
Nothing(), WasmPreBarrierKind::None); if (!store) { returnfalse;
}
curBlock_->add(store);
// Call the post-write barrier if (!postBarrierWholeCell(bytecodeOffset, exception, argValues[i])) { returnfalse;
}
}
// Throw the exception return throwFrom(exception, tag);
}
[[nodiscard]] bool emitThrowRef(MDefinition* exnRef) { if (inDeadCode()) { returntrue;
}
// The exception must be non-null
exnRef = refAsNonNull(exnRef); if (!exnRef) { returnfalse;
}
// Call Instance::throwException to perform tag unpacking and throw the // exception if (!emitInstanceCall1(readBytecodeOffset(), SASigThrowException, exnRef)) { returnfalse;
}
unreachableTrap();
// Check if there is a local catching try control, and if so, then add a // pad-patch to its tryPadPatches.
ControlInstructionVector* tryLandingPadPatches; if (inTryBlock(&tryLandingPadPatches)) { // Set the pending exception state, the landing pad will read from this if (!setPendingExceptionState(exn, tag)) { returnfalse;
}
// End with a pending jump to the landing pad if (!endWithPadPatch(tryLandingPadPatches)) { returnfalse;
}
curBlock_ = nullptr; returntrue;
}
// If there is no surrounding catching block, call an instance method to // throw the exception. if (!emitInstanceCall1(readBytecodeOffset(), SASigThrowException, exn)) { returnfalse;
}
unreachableTrap();
curBlock_ = nullptr; returntrue;
}
[[nodiscard]] bool emitRethrow(uint32_t relativeDepth) { if (inDeadCode()) { returntrue;
}
// Given a (StorageType, FieldExtension) pair, produce the (MIRType, // MWideningOp) pair that will give the correct operation for reading the // value from memory. staticvoid fieldLoadInfoToMIR(StorageType type, FieldWideningOp wideningOp,
MIRType* mirType, MWideningOp* mirWideningOp) { switch (type.kind()) { case StorageType::I8: { switch (wideningOp) { case FieldWideningOp::Signed:
*mirType = MIRType::Int32;
*mirWideningOp = MWideningOp::FromS8; return; case FieldWideningOp::Unsigned:
*mirType = MIRType::Int32;
*mirWideningOp = MWideningOp::FromU8; return; default:
MOZ_CRASH();
}
} case StorageType::I16: { switch (wideningOp) { case FieldWideningOp::Signed:
*mirType = MIRType::Int32;
*mirWideningOp = MWideningOp::FromS16; return; case FieldWideningOp::Unsigned:
*mirType = MIRType::Int32;
*mirWideningOp = MWideningOp::FromU16; return; default:
MOZ_CRASH();
}
} default: { switch (wideningOp) { case FieldWideningOp::None:
*mirType = type.toMIRType();
*mirWideningOp = MWideningOp::None; return; default:
MOZ_CRASH();
}
}
}
}
// Given a StorageType, return the Scale required when accessing array // elements of this type. static Scale scaleFromFieldType(StorageType type) { if (type.kind() == StorageType::V128) { // V128 is accessed differently, so this scale will not be used. return Scale::Invalid;
} return ShiftToScale(type.indexingShift());
}
// Given a StorageType, produce the MNarrowingOp required for writing the // value to memory. static MNarrowingOp fieldStoreInfoToMIR(StorageType type) { switch (type.kind()) { case StorageType::I8: return MNarrowingOp::To8; case StorageType::I16: return MNarrowingOp::To16; default: return MNarrowingOp::None;
}
}
// Generate a write of `value` at address `base + offset`, where `offset` is // known at JIT time. If the written value is a reftype, the previous value // at `base + offset` will be retrieved and handed off to the post-write // barrier. `keepAlive` will be referenced by the instruction so as to hold // it live (from the GC's point of view).
[[nodiscard]] bool writeGcValueAtBasePlusOffset(
uint32_t lineOrBytecode, StorageType type, MDefinition* keepAlive,
AliasSet::Flag aliasBitset, MDefinition* value, MDefinition* base,
uint32_t offset, uint32_t fieldIndex, bool needsTrapInfo,
WasmPreBarrierKind preBarrierKind, WasmPostBarrierKind postBarrierKind) {
MOZ_ASSERT(aliasBitset != 0);
MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
MOZ_ASSERT(type.widenToValType().toMIRType() == value->type());
MNarrowingOp narrowingOp = fieldStoreInfoToMIR(type);
if (!type.isRefRepr()) {
MaybeTrapSiteDesc maybeTrap; if (needsTrapInfo) {
maybeTrap.emplace(trapSiteDesc());
}
// Otherwise it's a ref store. Load the previous value so we can show it // to the post-write barrier. // // Optimisation opportunity: for the case where this field write results // from struct.new, the old value is always zero. So we should synthesise // a suitable zero constant rather than reading it from the object. See // also bug 1799999.
MOZ_ASSERT(narrowingOp == MNarrowingOp::None);
MOZ_ASSERT(type.widenToValType() == type.valType());
// Store the new value auto* store = MWasmStoreFieldRef::New(
alloc(), instancePointer_, base, keepAlive, offset,
mozilla::Some(fieldIndex), value, AliasSet::Store(aliasBitset),
mozilla::Some(trapSiteDesc()), preBarrierKind); if (!store) { returnfalse;
}
curBlock_->add(store);
// Call the post-write barrier switch (postBarrierKind) { case WasmPostBarrierKind::WholeCell: return postBarrierWholeCell(lineOrBytecode, keepAlive, value); case WasmPostBarrierKind::Edge:
MOZ_CRASH("WasmPostBarrierKind::Edge not supported"); case WasmPostBarrierKind::None: returntrue; default:
MOZ_CRASH("Bad postBarrierKind");
}
}
// Generate a write of `value` at address `base + index * scale`, where // `scale` is known at JIT-time. If the written value is a reftype, the // previous value at `base + index * scale` will be retrieved and handed off // to the post-write barrier. `keepAlive` will be referenced by the // instruction so as to hold it live (from the GC's point of view).
[[nodiscard]] bool writeGcValueAtBasePlusScaledIndex(
uint32_t lineOrBytecode, StorageType type, MDefinition* keepAlive,
AliasSet::Flag aliasBitset, MDefinition* value, MDefinition* base,
uint32_t scale, MDefinition* index, WasmPreBarrierKind preBarrierKind,
WasmPostBarrierKind postBarrierKind) {
MOZ_ASSERT(aliasBitset != 0);
MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
MOZ_ASSERT(type.widenToValType().toMIRType() == value->type());
MOZ_ASSERT(scale == 1 || scale == 2 || scale == 4 || scale == 8 ||
scale == 16);
// Store the new value auto* store = MWasmStoreElementRef::New(
alloc(), instancePointer_, base, index, value, keepAlive,
AliasSet::Store(aliasBitset), mozilla::Some(trapSiteDesc()),
preBarrierKind); if (!store) { returnfalse;
}
curBlock_->add(store);
switch (postBarrierKind) { case WasmPostBarrierKind::WholeCell: return postBarrierWholeCell(lineOrBytecode, keepAlive, value); case WasmPostBarrierKind::Edge: return postBarrierEdgeAtIndex(lineOrBytecode, keepAlive, base, index, sizeof(void*), value); case WasmPostBarrierKind::None: returntrue; default:
MOZ_CRASH("Bad postBarrierKind");
}
}
// Generate a read from address `base + offset`, where `offset` is known at // JIT time. The loaded value will be widened as described by `type` and // `fieldWideningOp`. `keepAlive` will be referenced by the instruction so as // to hold it live (from the GC's point of view).
[[nodiscard]] MDefinition* readGcValueAtBasePlusOffset(
StorageType type, FieldWideningOp fieldWideningOp, MDefinition* keepAlive,
AliasSet::Flag aliasBitset, MDefinition* base, uint32_t offset,
uint32_t fieldIndex, bool needsTrapInfo) {
MOZ_ASSERT(aliasBitset != 0);
MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
MIRType mirType;
MWideningOp mirWideningOp;
fieldLoadInfoToMIR(type, fieldWideningOp, &mirType, &mirWideningOp);
MaybeTrapSiteDesc maybeTrap; if (needsTrapInfo) {
maybeTrap.emplace(trapSiteDesc());
}
// Generate a read from address `base + index * scale`, where `scale` is // known at JIT-time. The loaded value will be widened as described by // `type` and `fieldWideningOp`. `keepAlive` will be referenced by the // instruction so as to hold it live (from the GC's point of view).
[[nodiscard]] MDefinition* readGcArrayValueAtIndex(
StorageType type, FieldWideningOp fieldWideningOp, MDefinition* keepAlive,
AliasSet::Flag aliasBitset, MDefinition* base, MDefinition* index) {
MOZ_ASSERT(aliasBitset != 0);
MOZ_ASSERT(keepAlive->type() == MIRType::WasmAnyRef);
uint32_t readAllocSiteIndex(uint32_t typeIndex) { if (!codeTailMeta() || !codeTailMeta()->hasFuncDefAllocSites()) { // For single tier of optimized compilation, there are no assigned alloc // sites, using type index as alloc site. return typeIndex;
}
AllocSitesRange rangeInModule =
codeTailMeta()->getFuncDefAllocSites(funcIndex());
uint32_t localIndex = numAllocSites_++;
MOZ_RELEASE_ASSERT(localIndex < rangeInModule.length); return rangeInModule.begin + localIndex;
}
// 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 StructType& structType,
uint32_t fieldIndex, MDefinition* structObject, MDefinition* value,
WasmPreBarrierKind preBarrierKind) {
StorageType fieldType = structType.fields_[fieldIndex].type;
FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
uint32_t areaOffset = path.hasOOL() ? path.oolOffset() : path.ilOffset();
// Make `base` point at the first byte of either the struct object as a // whole or of the out-of-line data area.
MDefinition* base; bool needsTrapInfo; if (path.hasOOL()) { // The path has two components, of which the first (the IL component) is // the offset where the OOL pointer is stored. Hence `path.ilOffset()`. auto* loadDataPointer = MWasmLoadField::New(
alloc(), structObject, nullptr, path.ilOffset(), mozilla::Nothing(),
MIRType::WasmStructData, MWideningOp::None,
AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
mozilla::Some(trapSiteDesc())); if (!loadDataPointer) { returnfalse;
}
curBlock_->add(loadDataPointer);
base = loadDataPointer;
needsTrapInfo = false;
} else {
base = structObject;
needsTrapInfo = true;
} // The transaction is to happen at `base + areaOffset`, so to speak.
// The alias set denoting the field's location, although lacking a // Load-vs-Store indication at this point.
AliasSet::Flag fieldAliasSet = path.hasOOL()
? AliasSet::WasmStructOutlineDataArea
: AliasSet::WasmStructInlineDataArea;
// Helper function for EmitStructGet: given a MIR pointer to a // WasmStructObject, a field descriptor and a field widening operation, // generate MIR to read the value from the relevant field in the object.
[[nodiscard]] MDefinition* readValueFromStructField( const StructType& structType, uint32_t fieldIndex,
FieldWideningOp wideningOp, MDefinition* structObject) {
StorageType fieldType = structType.fields_[fieldIndex].type;
FieldAccessPath path = structType.fieldAccessPaths_[fieldIndex];
uint32_t areaOffset = path.hasOOL() ? path.oolOffset() : path.ilOffset();
// Make `base` point at the first byte of either the struct object as a // whole or of the out-of-line data area.
MDefinition* base; bool needsTrapInfo; if (path.hasOOL()) { // The path has two components, of which the first (the IL component) is // the offset where the OOL pointer is stored. Hence `path.ilOffset()`. auto* loadDataPointer = MWasmLoadField::New(
alloc(), structObject, nullptr, path.ilOffset(), mozilla::Nothing(),
MIRType::WasmStructData, MWideningOp::None,
AliasSet::Load(AliasSet::WasmStructOutlineDataPointer),
mozilla::Some(trapSiteDesc())); if (!loadDataPointer) { return nullptr;
}
curBlock_->add(loadDataPointer);
base = loadDataPointer;
needsTrapInfo = false;
} else {
base = structObject;
needsTrapInfo = true;
} // The transaction is to happen at `base + areaOffset`, so to speak.
// The alias set denoting the field's location, although lacking a // Load-vs-Store indication at this point.
AliasSet::Flag fieldAliasSet = path.hasOOL()
? AliasSet::WasmStructOutlineDataArea
: AliasSet::WasmStructInlineDataArea;
// 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;
}
// Given `arrayObject`, the address of a WasmArrayObject, generate MIR to // return the contents of the WasmArrayObject::numElements_ field. // Adds trap site info for the null check.
[[nodiscard]] MDefinition* getWasmArrayObjectNumElements(
MDefinition* arrayObject) {
MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);
// Given `arrayObject`, the address of a WasmArrayObject, generate MIR to // return the contents of the WasmArrayObject::data_ field.
[[nodiscard]] MDefinition* getWasmArrayObjectData(MDefinition* arrayObject) {
MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);
auto* data = MWasmLoadField::New(
alloc(), arrayObject, nullptr, WasmArrayObject::offsetOfData(),
mozilla::Nothing(), MIRType::WasmArrayData, MWideningOp::None,
AliasSet::Load(AliasSet::WasmArrayDataPointer),
mozilla::Some(trapSiteDesc())); if (!data) { return nullptr;
}
curBlock_->add(data);
return data;
}
// Given a JIT-time-known type index `typeIndex` and a run-time known number // of elements `numElements`, create MIR to allocate a new wasm array, // possibly initialized with `typeIndex`s default value.
[[nodiscard]] MDefinition* createArrayObject(uint32_t typeIndex,
uint32_t allocSiteIndex,
MDefinition* numElements, bool zeroFields) {
MDefinition* allocSite = loadAllocSiteInstanceData(allocSiteIndex); if (!allocSite) { return nullptr;
}
// This emits MIR to perform several actions common to array loads and // stores. Given `arrayObject`, that points to a WasmArrayObject, and an // index value `index`, it: // // * Generates a trap if the array pointer is null // * Gets the size of the array // * Emits a bounds check of `index` against the array size // * Retrieves the OOL object pointer from the array // * Includes check for null via signal handler. // // The returned value is for the OOL object pointer.
[[nodiscard]] MDefinition* setupForArrayAccess(MDefinition* arrayObject,
MDefinition* index) {
MOZ_ASSERT(arrayObject->type() == MIRType::WasmAnyRef);
MOZ_ASSERT(index->type() == MIRType::Int32);
// Check for null is done in getWasmArrayObjectNumElements.
// Get the size value for the array.
MDefinition* numElements = getWasmArrayObjectNumElements(arrayObject); if (!numElements) { return nullptr;
}
// Create a bounds check. auto* boundsCheck =
MWasmBoundsCheck::New(alloc(), index, numElements, trapSiteDesc(),
MWasmBoundsCheck::Target::Other); if (!boundsCheck) { return nullptr;
}
curBlock_->add(boundsCheck);
// Get the address of the first byte of the (OOL) data area. return getWasmArrayObjectData(arrayObject);
}
// Make `arrayBase` point at the first byte of the (OOL) data area.
MDefinition* arrayBase = getWasmArrayObjectData(arrayObject); if (!arrayBase) { returnfalse;
}
// We have: // arrayBase : TargetWord // index : Int32 // numElements : Int32 // val : <any StorageType> // $elemSize = arrayType.elementType_.size(); 1, 2, 4, 8 or 16 // // Generate MIR: // <in current block> // limit : Int32 = index + numElements // if (limit == index) goto after; // skip loop if trip count == 0 // loop: // indexPhi = phi(index, indexNext) // arrayBase[index * $elemSize] = val // indexNext = indexPhi + 1 // if (indexNext <u limit) goto loop; // after: // // We construct the loop "manually" rather than using // FunctionCompiler::{startLoop,closeLoop} as the latter have awareness of // the wasm view of loops, whereas the loop we're building here is not a // wasm-level loop. // ==== Create the "loop" and "after" blocks ====
MBasicBlock* loopBlock; if (!newBlock(curBlock_, &loopBlock, MBasicBlock::LOOP_HEADER)) { returnfalse;
}
MBasicBlock* afterBlock; if (!newBlock(loopBlock, &afterBlock)) { returnfalse;
}
// ==== Fill in the remainder of the block preceding the loop ====
MAdd* limit = MAdd::NewWasm(alloc(), index, numElements, MIRType::Int32); if (!limit) { returnfalse;
}
curBlock_->add(limit);
// Note: the comparison (and eventually the entire initialisation loop) will // be folded out in the case where the number of elements is zero. // See MCompare::tryFoldEqualOperands.
MDefinition* limitEqualsBase =
compare(limit, index, JSOp::StrictEq, MCompare::Compare_UInt32); if (!limitEqualsBase) { returnfalse;
}
MTest* skipIfLimitEqualsBase =
MTest::New(alloc(), limitEqualsBase, afterBlock, loopBlock); if (!skipIfLimitEqualsBase) { returnfalse;
}
curBlock_->end(skipIfLimitEqualsBase); if (!afterBlock->addPredecessor(alloc(), curBlock_)) { returnfalse;
}
// ==== Fill in the loop block as best we can ====
curBlock_ = loopBlock;
MPhi* indexPhi = MPhi::New(alloc(), MIRType::Int32); if (!indexPhi) { returnfalse;
} if (!indexPhi->reserveLength(2)) { returnfalse;
}
indexPhi->addInput(index);
curBlock_->addPhi(indexPhi);
curBlock_->setLoopDepth(rootCompiler_.loopDepth() + 1);
[[nodiscard]] bool createArrayCopy(uint32_t lineOrBytecode,
MDefinition* dstArrayObject,
MDefinition* dstArrayIndex,
MDefinition* srcArrayObject,
MDefinition* srcArrayIndex,
MDefinition* numElements, int32_t elemSize, bool elemsAreRefTyped) { // Check for null is done in getWasmArrayObjectNumElements.
// Get the arrays' actual sizes.
MDefinition* dstNumElements = getWasmArrayObjectNumElements(dstArrayObject); if (!dstNumElements) { returnfalse;
}
MDefinition* srcNumElements = getWasmArrayObjectNumElements(srcArrayObject); if (!srcNumElements) { returnfalse;
}
// Create the bounds checks.
MInstruction* dstBoundsCheck = MWasmBoundsCheckRange32::New(
alloc(), dstArrayIndex, numElements, dstNumElements, trapSiteDesc()); if (!dstBoundsCheck) { returnfalse;
}
curBlock_->add(dstBoundsCheck);
/*********************************************** WasmGC: other helpers ***/
// Generate MIR that attempts to cast `ref` to `castToTypeDef`. If the // cast fails, we trap. If it succeeds, then `ref` can be assumed to // have a type that is a subtype of (or the same as) `castToTypeDef` after // this point.
[[nodiscard]] MDefinition* refCast(MDefinition* ref, RefType destType) {
MInstruction* cast = nullptr; if (destType.isTypeRef()) {
uint32_t typeIndex = codeMeta().types->indexOf(*destType.typeDef());
MDefinition* superSTV = loadSuperTypeVector(typeIndex); if (!superSTV) { return nullptr;
}
cast = MWasmRefCastConcrete::New(alloc(), ref, superSTV, destType,
trapSiteDesc());
} else {
cast = MWasmRefCastAbstract::New(alloc(), ref, destType, trapSiteDesc());
}
if (!cast) { return nullptr;
}
curBlock_->add(cast); return cast;
}
// Generate MIR that computes a boolean value indicating whether or not it // is possible to cast `ref` to `destType`.
[[nodiscard]] MDefinition* refTest(MDefinition* ref, RefType destType) {
MInstruction* isSubTypeOf = nullptr; if (destType.isTypeRef()) {
uint32_t typeIndex = codeMeta().types->indexOf(*destType.typeDef());
MDefinition* superSTV = loadSuperTypeVector(typeIndex); if (!superSTV) { return nullptr;
}
isSubTypeOf = MWasmRefTestConcrete::New(alloc(), ref, superSTV, destType);
} else {
isSubTypeOf = MWasmRefTestAbstract::New(alloc(), ref, destType);
}
MOZ_ASSERT(isSubTypeOf);
// Generates MIR for br_on_cast and br_on_cast_fail.
[[nodiscard]] bool brOnCastCommon(bool onSuccess, uint32_t labelRelativeDepth,
RefType sourceType, RefType destType, const ResultType& labelType, const DefVector& values,
BranchHint branchHint) { if (inDeadCode()) { returntrue;
}
MBasicBlock* fallthroughBlock = nullptr; if (!newBlock(curBlock_, &fallthroughBlock)) { returnfalse;
}
// `values` are the values in the top block-value on the stack. Since the // argument to `br_on_cast{_fail}` is at the top of the stack, it is the // last element in `values`. // // For both br_on_cast and br_on_cast_fail, the OpIter validation routines // ensure that `values` is non-empty (by rejecting the case // `labelType->length() < 1`) and that the last value in `values` is // reftyped.
MOZ_RELEASE_ASSERT(values.length() > 0);
MDefinition* ref = values.back();
MOZ_ASSERT(ref->type() == MIRType::WasmAnyRef);
MDefinition* success = refTest(ref, destType); if (!success) { returnfalse;
}
MTest* test; if (onSuccess) {
test = MTest::New(alloc(), success, nullptr, fallthroughBlock); if (!test || !addControlFlowPatch(test, labelRelativeDepth,
MTest::TrueBranchIndex, branchHint)) { returnfalse;
}
} else {
test = MTest::New(alloc(), success, fallthroughBlock, nullptr); if (!test || !addControlFlowPatch(test, labelRelativeDepth,
MTest::FalseBranchIndex, branchHint)) { returnfalse;
}
}
// 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(); }
CallRefHint readCallRefHint() { // We don't track anything if we're not using lazy tiering if (compilerEnv().mode() != CompileMode::LazyTiering) { return CallRefHint();
}
MBasicBlock* elseBlock; if (!branchAndStartThen(condition, &elseBlock)) { returnfalse;
}
// Store the branch hint in the basic block. if (!inDeadCode() && branchHint != BranchHint::Invalid) { if (branchHint == BranchHint::Likely) {
getCurBlock()->setFrequency(Frequency::Likely);
} elseif (branchHint == BranchHint::Unlikely) {
getCurBlock()->setFrequency(Frequency::Unlikely);
}
}
Control& control = iter().controlItem();
MBasicBlock* block = control.block;
if (!pushDefs(preJoinDefs)) { returnfalse;
}
// Every label case is responsible to pop the control item at the appropriate // time for the label case
DefVector postJoinDefs; switch (kind) { case LabelKind::Body: {
MOZ_ASSERT(!control.tryControl); if (!emitBodyRethrowPad(control)) { returnfalse;
} if (!finishBlock(&postJoinDefs)) { returnfalse;
} if (!returnValues(std::move(postJoinDefs))) { returnfalse;
}
iter().popEnd();
MOZ_ASSERT(iter().controlStackEmpty()); return iter().endFunction(iter().end());
} case LabelKind::Block:
MOZ_ASSERT(!control.tryControl); if (!finishBlock(&postJoinDefs)) { returnfalse;
}
iter().popEnd(); break; case LabelKind::Loop:
MOZ_ASSERT(!control.tryControl); if (!closeLoop(block, &postJoinDefs)) { returnfalse;
}
iter().popEnd(); break; case LabelKind::Then: {
MOZ_ASSERT(!control.tryControl); // If we didn't see an Else, create a trivial else block so that we create // a diamond anyway, to preserve Ion invariants. if (!switchToElse(block, &block)) { returnfalse;
}
if (!pushDefs(resultsForEmptyElse)) { returnfalse;
}
if (!joinIfElse(block, &postJoinDefs)) { returnfalse;
}
iter().popEnd(); break;
} case LabelKind::Else:
MOZ_ASSERT(!control.tryControl); if (!joinIfElse(block, &postJoinDefs)) { returnfalse;
}
iter().popEnd(); break; case LabelKind::Try: case LabelKind::Catch: case LabelKind::CatchAll:
MOZ_ASSERT(control.tryControl); if (!finishTryCatch(kind, control, &postJoinDefs)) { returnfalse;
}
rootCompiler().freeTryControl(std::move(control.tryControl));
iter().popEnd(); break; case LabelKind::TryTable:
MOZ_ASSERT(control.tryControl); if (!finishTryTable(control, &postJoinDefs)) { returnfalse;
}
rootCompiler().freeTryControl(std::move(control.tryControl));
iter().popEnd(); break;
}
// If all the targets are the same, or there are no targets, we can just // use a goto. This is not just an optimization: MaybeFoldConditionBlock // assumes that tables have more than one successor. bool allSameDepth = true; for (uint32_t depth : depths) { if (depth != defaultDepth) {
allSameDepth = false; break;
}
}
if (allSameDepth) { return br(defaultDepth, branchValues);
}
// Pushing the results of the previous block, to properly join control flow // after the try and after each handler, as well as potential control flow // patches from other instrunctions. This is similar to what is done for // if-then-else control flow and for most other control control flow joins. if (!pushDefs(tryValues)) { returnfalse;
}
// Pushing the results of the previous block, to properly join control flow // after the try and after each handler, as well as potential control flow // patches from other instrunctions. if (!pushDefs(tryValues)) { returnfalse;
}
Control& control = iter().controlItem();
MBasicBlock* block = control.block;
MOZ_ASSERT(control.tryControl);
// Unless the entire try-delegate is dead code, delegate any pad-patches from // this try to the next try-block above relativeDepth. if (block) {
ControlInstructionVector& padPatches =
control.tryControl->landingPadPatches; if (!delegatePadPatches(padPatches, relativeDepth)) { returnfalse;
}
}
rootCompiler().freeTryControl(std::move(control.tryControl));
iter().popDelegate();
// Push the results of the previous block, and join control flow with // potential control flow patches from other instrunctions in the try code. // This is similar to what is done for EmitEnd. if (!pushDefs(tryValues)) { returnfalse;
}
DefVector postJoinDefs; if (!finishBlock(&postJoinDefs)) { returnfalse;
}
MOZ_ASSERT_IF(!inDeadCode(), postJoinDefs.length() == resultType.length());
iter().setResults(postJoinDefs.length(), postJoinDefs);
bool FunctionCompiler::emitGetGlobal() {
uint32_t id; if (!iter().readGetGlobal(&id)) { returnfalse;
}
const GlobalDesc& global = codeMeta().globals[id]; if (!global.isConstant()) {
iter().setResult(loadGlobalVar(global)); returntrue;
}
LitVal value = global.constantValue();
MDefinition* result; switch (value.type().kind()) { case ValType::I32:
result = constantI32(int32_t(value.i32())); break; case ValType::I64:
result = constantI64(int64_t(value.i64())); break; case ValType::F32:
result = constantF32(value.f32()); break; case ValType::F64:
result = constantF64(value.f64()); break; case ValType::V128: #ifdef ENABLE_WASM_SIMD
result = constantV128(value.v128()); break; #else return iter().fail("Ion has no SIMD support yet"); #endif case ValType::Ref:
MOZ_ASSERT(value.ref().isNull());
result = constantNullRef(MaybeRefType(value.type().refType())); break; default:
MOZ_CRASH("unexpected type in EmitGetGlobal");
}
MDefinition* lhs;
MDefinition* rhs; // This call to readBinary assumes both operands have the same type. if (!iter().readBinary(ValType::fromMIRType(callee.argTypes[0]), &lhs,
&rhs)) { returnfalse;
}
// Compute the number of copies of each width we will need to do
size_t remainder = length; #ifdef ENABLE_WASM_SIMD
size_t numCopies16 = 0; if (MacroAssembler::SupportsFastUnalignedFPAccesses()) {
numCopies16 = remainder / sizeof(V128);
remainder %= sizeof(V128);
} #endif #ifdef JS_64BIT
size_t numCopies8 = remainder / sizeof(uint64_t);
remainder %= sizeof(uint64_t); #endif
size_t numCopies4 = remainder / sizeof(uint32_t);
remainder %= sizeof(uint32_t);
size_t numCopies2 = remainder / sizeof(uint16_t);
remainder %= sizeof(uint16_t);
size_t numCopies1 = remainder;
// Load all source bytes from low to high using the widest transfer width we // can for the system. We will trap without writing anything if any source // byte is out-of-bounds.
size_t offset = 0;
DefVector loadedValues;
#ifdef ENABLE_WASM_SIMD for (uint32_t i = 0; i < numCopies16; i++) {
MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
trapSiteDesc(), hugeMemoryEnabled(memoryIndex)); auto* loadValue = load(src, &access, ValType::V128); if (!loadValue || !loadedValues.append(loadValue)) { returnfalse;
}
offset += sizeof(V128);
} #endif
#ifdef JS_64BIT for (uint32_t i = 0; i < numCopies8; i++) {
MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
trapSiteDesc(), hugeMemoryEnabled(memoryIndex)); auto* loadValue = load(src, &access, ValType::I64); if (!loadValue || !loadedValues.append(loadValue)) { returnfalse;
}
offset += sizeof(uint64_t);
} #endif
for (uint32_t i = 0; i < numCopies4; i++) {
MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
trapSiteDesc(), hugeMemoryEnabled(memoryIndex)); auto* loadValue = load(src, &access, ValType::I32); if (!loadValue || !loadedValues.append(loadValue)) { returnfalse;
}
// 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;
// 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 (table.elemType().tableRepr() == TableRepr::Ref) {
MDefinition* ret = tableGetAnyRef(tableIndex, address); if (!ret) { returnfalse;
}
iter().setResult(ret); returntrue;
}
uint32_t bytecodeOffset = readBytecodeOffset();
MDefinition* address32 = clampTableAddressToI32(table.addressType(), address); if (!address32) { returnfalse;
}
MDefinition* tableIndexArg = constantI32(int32_t(tableIndex)); if (!tableIndexArg) { returnfalse;
}
// The return value here is either null, denoting an error, or a short-lived // pointer to a location containing a possibly-null ref.
MDefinition* ret; if (!emitInstanceCall2(bytecodeOffset, SASigTableGet, address32,
tableIndexArg, &ret)) { returnfalse;
}
bool FunctionCompiler::emitTableSize() {
uint32_t tableIndex; if (!iter().readTableSize(&tableIndex)) { returnfalse;
}
if (inDeadCode()) { returntrue;
}
MDefinition* length = loadTableLength(tableIndex); if (!length) { returnfalse;
}
iter().setResult(length); returntrue;
}
bool FunctionCompiler::emitRefFunc() {
uint32_t funcIndex; if (!iter().readRefFunc(&funcIndex)) { returnfalse;
}
if (inDeadCode()) { returntrue;
}
uint32_t bytecodeOffset = readBytecodeOffset();
MDefinition* funcIndexArg = constantI32(int32_t(funcIndex)); if (!funcIndexArg) { returnfalse;
}
// The return value here is either null, denoting an error, or a short-lived // pointer to a location containing a possibly-null ref.
MDefinition* ret; if (!emitInstanceCall1(bytecodeOffset, SASigRefFunc, funcIndexArg, &ret)) { returnfalse;
}
iter().setResult(ret); returntrue;
}
bool FunctionCompiler::emitRefNull() {
RefType type; if (!iter().readRefNull(&type)) { returnfalse;
}
// Some types are not castable (right now just continuations). The casting // machinery cannot handle them. So emit a direct null-check for now. if (!sourceType.isCastable()) {
MDefinition* isNull = compareIsNull(input, JSOp::Eq); if (!isNull) { returnfalse;
}
iter().setResult(isNull); returntrue;
}
// ref.is_null is implemented as a ref.test against the bottom type of the // input ref's hierarchy. This will codegen to a simple null comparison, but // allows this op to participate in other optimizations surrounding ref.test // and ref.cast.
MDefinition* test = refTest(input, sourceType.bottomType()); if (!test) { returnfalse;
}
iter().setResult(test); returntrue;
}
////////////////////////////////////////////////////////////////////////////// // // Wide Arithmetic support
#else // 32 bit implementation. Call a helper function. The arguments and return // value are passed in Instance::baselineScratchWords_[0..7]; see // Instance::addSubI128 for details.
MDefinition* storeSequence[4] = {xLo, xHi, yLo, yHi}; for (int i = 0; i < 4; i++) { auto* store = MWasmStoreInstanceScratch2xI32::New(
alloc(), /*byteOffset=*/i * 8, storeSequence[i], instancePointer_); if (!store) { returnfalse;
}
curBlock_->add(store);
}
bool FunctionCompiler::emitI64MulWide(bool isSigned) {
MDefinition* x;
MDefinition* y; if (!iter().readBinaryI64Wide(&x, &y)) { returnfalse;
} if (inDeadCode()) { returntrue;
}
// Compute zHi:zLo = x *widen y
MInstruction* zLo;
MInstruction* zHi;
#ifdef JS_64BIT // 64 bit implementation. Produce inline code.
// We can compute the low and high halves in either order. However, the // RiscV specification advises computing the high half first, in the hope // that microarchitectures can merge it with the immediately following low // half multiply, hence avoiding the duplicate multiply.
zHi = MWasmMulI64WideHI64::New(alloc(), x, y, isSigned); if (!zHi) { returnfalse;
}
curBlock_->add(zHi);
zLo = MMul::NewWasm(alloc(), x, y, MIRType::Int64, /*mode=*/MMul::Normal, /*mustPreserveNaN=*/false); if (!zLo) { returnfalse;
}
curBlock_->add(zLo);
#else // 32 bit implementation. Call a helper function. The arguments and return // value are passed in Instance::baselineScratchWords_[0..4]; see // Instance::mulI64Wide for details. auto* store = MWasmStoreInstanceScratch2xI32::New(alloc(), /*byteOffset=*/0, x,
instancePointer_); if (!store) { returnfalse;
}
curBlock_->add(store);
store = MWasmStoreInstanceScratch2xI32::New(alloc(), /*byteOffset=*/8, y,
instancePointer_); if (!store) { returnfalse;
}
curBlock_->add(store);
// Speculatively inline a call_refs that are likely to target the expected // function index in this module. A fallback for if the actual callee is not // any of the speculated expected callees is always generated. This leads to a // control flow chain that is roughly: // // if (ref.func $expectedFuncIndex_1) == actualCalleeFunc: // (call_inline $expectedFuncIndex1) // else if (ref.func $expectedFuncIndex_2) == actualCalleeFunc: // (call_inline $expectedFuncIndex2) // ... // else: // (call_ref actualCalleeFunc) // bool FunctionCompiler::emitSpeculativeInlineCallRef(
uint32_t bytecodeOffset, const FuncType& funcType,
CallRefHint expectedFuncIndices, MDefinition* actualCalleeFunc, const DefVector& args, DefVector* results) { // There must be at least one speculative target.
MOZ_ASSERT(!expectedFuncIndices.empty());
// Perform an up front null check on the callee function reference.
actualCalleeFunc = refAsNonNull(actualCalleeFunc); if (!actualCalleeFunc) { returnfalse;
}
for (uint32_t i = 0; i < expectedFuncIndices.length(); i++) {
uint32_t funcIndex = expectedFuncIndices.get(i);
// Load the cached value of `ref.func $expectedFuncIndex` for comparing // against `actualCalleeFunc`. This cached value may be null if the // `ref.func` for the expected function has not been executed in this // runtime session. // // This is okay because we have done a null check on the `actualCalleeFunc` // already and so comparing it against a null expected callee func will // return false and fall back to the general case. This can only happen if // we've deserialized a cached module in a different session, and then run // the code without ever acquiring a reference to the expected function. In // that case, the expected callee could never be the target of this // call_ref, so performing the fallback path is the right thing to do // anyways.
MDefinition* expectedCalleeFunc = loadCachedRefFunc(funcIndex); if (!expectedCalleeFunc) { returnfalse;
}
// Check if the callee funcref we have is equals to the expected callee // funcref we're inlining.
MDefinition* isExpectedCallee =
compare(actualCalleeFunc, expectedCalleeFunc, JSOp::Eq,
MCompare::Compare_WasmAnyRef); if (!isExpectedCallee) { returnfalse;
}
// Start a 'then' block, which will have the inlined code
MBasicBlock* elseBlock; if (!branchAndStartThen(isExpectedCallee, &elseBlock)) { returnfalse;
}
// Inline the expected callee as we do with direct calls
DefVector inlineResults; if (!emitInlineCall(funcType, funcIndex,
InliningHeuristics::CallKind::CallRef, args,
&inlineResults)) { returnfalse;
}
// Push the results for joining with the 'else' block if (!pushDefs(inlineResults)) { returnfalse;
}
// Switch to the 'else' block which will have, either the check for the // next target, or the fallback `call_ref` if we're out of targets. if (!switchToElse(elseBlock, &elseBlock)) { returnfalse;
}
elseBlocks.infallibleAppend(elseBlock);
}
// The block that performs the fallback call should be cold.
curBlock_->setFrequency(Frequency::Unlikely);
// Push the results for joining with the 'then' block if (!pushDefs(callResults)) { returnfalse;
}
// Join the various branches together for (uint32_t i = elseBlocks.length() - 1; i != 0; i--) {
DefVector results; if (!joinIfElse(elseBlocks[i], &results) || !pushDefs(results)) { returnfalse;
}
} return joinIfElse(elseBlocks[0], results);
}
// Ask the inlining heuristics which entries in `hint` we are allowed to // inline.
CallRefHint approved =
auditInlineableCallees(InliningHeuristics::CallKind::CallRef, hint); if (!approved.empty()) {
DefVector results; if (!emitSpeculativeInlineCallRef(bytecodeOffset, funcType, approved,
callee, args, &results)) { returnfalse;
}
iter().setResults(results.length(), results); returntrue;
}
// And fill in the fields. for (uint32_t fieldIndex = 0; fieldIndex < structType.fields_.length();
fieldIndex++) { if (!mirGen().ensureBallast()) { returnfalse;
} if (!writeValueToStructField(lineOrBytecode, structType, fieldIndex,
structObject, args[fieldIndex],
WasmPreBarrierKind::None)) { returnfalse;
}
}
iter().setResult(structObject); returntrue;
}
bool FunctionCompiler::emitStructNewDefault() {
uint32_t typeIndex; if (!iter().readStructNewDefault(&typeIndex)) { returnfalse;
}
// Check for null is done at writeValueToStructField.
// And fill in the field. const StructType& structType = (*codeMeta().types)[typeIndex].structType(); return writeValueToStructField(lineOrBytecode, structType, fieldIndex,
structObject, value,
WasmPreBarrierKind::Normal);
}
if (arrayType.elementType().isRefRepr()) { // Emit one whole-cell post barrier for the whole array, since there is just // one object and one value. if (!postBarrierWholeCell(lineOrBytecode, arrayObject, fillValue)) { returnfalse;
}
}
iter().setResult(arrayObject); returntrue;
}
bool FunctionCompiler::emitArrayNewDefault() { // This is almost identical to EmitArrayNew, except we skip the // initialisation loop.
uint32_t typeIndex;
MDefinition* numElements; if (!iter().readArrayNewDefault(&typeIndex, &numElements)) { returnfalse;
}
// Make `base` point at the first byte of the (OOL) data area.
MDefinition* base = getWasmArrayObjectData(arrayObject); if (!base) { returnfalse;
}
// Write each element in turn.
// How do we know that the offset expression `i * elemSize` below remains // within 2^31 (signed-i32) range? In the worst case we will have 16-byte // values, and there can be at most MaxFunctionBytes expressions, if it were // theoretically possible to generate one expression per instruction byte. // Hence the max offset we can be expected to generate is // `16 * MaxFunctionBytes`.
static_assert(16/* sizeof v128 */ * MaxFunctionBytes <=
MaxArrayPayloadBytes);
MOZ_RELEASE_ASSERT(numElements <= MaxFunctionBytes);
for (uint32_t i = 0; i < numElements; i++) { if (!mirGen().ensureBallast()) { returnfalse;
} // `i * elemSize` is made safe by the assertions above. if (!writeGcValueAtBasePlusOffset(
lineOrBytecode, elemType, arrayObject, AliasSet::WasmArrayDataArea,
values[numElements - 1 - i], base, i * elemSize, i, false,
WasmPreBarrierKind::None, WasmPostBarrierKind::WholeCell)) { returnfalse;
}
}
// Other values we need to pass to the instance call:
MDefinition* segIndexM = constantI32(int32_t(segIndex)); if (!segIndexM) { returnfalse;
}
// Create call: // Instance::arrayInitData(array:word, index:u32, segByteOffset:u32, // numElements:u32, segIndex:u32) If the requested size exceeds // MaxArrayPayloadBytes, the MIR generated by this call will trap. return emitInstanceCall5(lineOrBytecode, SASigArrayInitData, array,
arrayIndex, segOffset, length, segIndexM);
}
// Create the object null check and the array bounds check and get the OOL // data pointer.
MDefinition* base = setupForArrayAccess(arrayObject, index); if (!base) { returnfalse;
}
// And do the store. const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();
StorageType elemType = arrayType.elementType();
uint32_t elemSize = elemType.size();
MOZ_ASSERT(elemSize >= 1 && elemSize <= 16);
// Create the object null check and the array bounds check and get the data // pointer.
MDefinition* base = setupForArrayAccess(arrayObject, index); if (!base) { returnfalse;
}
// And do the load. const ArrayType& arrayType = (*codeMeta().types)[typeIndex].arrayType();
StorageType elemType = arrayType.elementType();
// TODO: Temporary restriction that cont type cannot have params or results. if (!contType.funcType().args().empty() ||
!contType.funcType().results().empty()) {
unimplementedTrap(); returntrue;
}
if (inDeadCode()) { returntrue;
}
MDefinition* result = nullptr; if (!emitInstanceCall1(readBytecodeOffset(), SASigContNew, func, &result)) { returnfalse;
}
iter().setResult(result);
// TODO: Not yet implemented
unimplementedTrap(); returntrue;
}
bool FunctionCompiler::emitStoreSuspendParams(
MDefinition* paramsArea, const ValTypeVector& suspendTagParams, const DefVector& suspendParams, MDefinition* suspendedCont) { // Last param is the cont, it goes first in stack results.
size_t paramsAreaOffset = 0;
MWasmStackResultArea::StackResult loc(paramsAreaOffset,
js::jit::MIRType::WasmAnyRef);
MWasmStoreStackResult* storeCont = MWasmStoreStackResult::New(
alloc(), paramsArea, paramsAreaOffset, suspendedCont); if (!storeCont) { returnfalse;
}
curBlock_->add(storeCont);
paramsAreaOffset = loc.endOffset();
// The rest are suspendTagParams, again in reverse order. for (uint32_t i = 0; i < suspendTagParams.length(); i++) { if (!mirGen().ensureBallast()) { returnfalse;
}
size_t reverseIndex = suspendTagParams.length() - i - 1;
// TODO: Temporary restriction that suspend tags cannot have results. if (!tagType.resultTypes().empty()) {
unimplementedTrap(); returntrue;
}
// TODO: Temporary restriction that we can't be in a try block yet. A // resume_throw will be able to trigger an exception that we need to handle. if (inTryCode()) {
unimplementedTrap(); returntrue;
}
// Load the tag we're going to search for.
MDefinition* tag = loadTag(tagIndex); if (!tag) { returnfalse;
}
// Search for the handler for this tag. When this suspend is used for JS-PI // this find handler will be infallible because we emit a different find // handler in emitGuardSuspending (which throws an exception instead of a // trap).
MWasmFindHandler* handler =
MWasmFindHandler::New(alloc(), instancePointer_, tag,
Trap::NullPointerDereference, trapSiteDesc()); if (!handler) { returnfalse;
}
curBlock_->add(handler);
// Load the paramsArea from the handler.
MWasmLoadInstance* paramsArea =
MWasmLoadInstance::New(alloc(), handler,
offsetof(wasm::Handler, target) +
offsetof(wasm::SwitchTarget, paramsArea),
MIRType::Pointer, AliasSet::None()); if (!paramsArea) { returnfalse;
}
curBlock_->add(paramsArea);
// Allocate a new continuation that will hold our stack when we suspend.
MDefinition* suspendedCont = nullptr; if (!emitInstanceCall0(readBytecodeOffset(), SASigContNewEmpty,
&suspendedCont)) { returnfalse;
}
// Store all the params into the handler params area. if (!emitStoreSuspendParams(paramsArea, tagType.argTypes(), suspendParams,
suspendedCont)) { returnfalse;
}
// Emit the suspend instruction.
MWasmSuspend* suspend =
MWasmSuspend::New(alloc(), instancePointer_, suspendedCont, handler,
callSiteDesc(CallSiteKind::StackSwitch)); if (!suspend) { returnfalse;
}
curBlock_->add(suspend);
// TODO: Temporary restriction that cont type cannot have params or results. constTypeDef& typeDef = codeMeta().types->type(typeIndex); const ContType& contType = typeDef.contType(); if (!contType.funcType().args().empty() ||
!contType.funcType().results().empty()) {
unimplementedTrap(); returntrue;
}
// TODO: Temporary restriction that suspend tags cannot have params or // results. for (const HandlerExpr& handler : handlers) { const TagDesc& tagDesc = codeMeta().tags[handler.tagIndex()]; const TagType& tagType = *tagDesc.type;
if (handler.isSwitch() || !tagType.resultTypes().empty()) {
unimplementedTrap(); returntrue;
}
}
// Start with a resume barrier which will mark the stack if it hasn't yet and // we're in an incremental GC.
MWasmResumeBarrier* barrier =
MWasmResumeBarrier::New(alloc(), instancePointer_, cont); if (!barrier) { returnfalse;
}
curBlock_->add(barrier);
// Last param is the cont, it goes first in stack results.
MWasmStackResultArea::StackResult loc(currentResultsAreaOffset,
js::jit::MIRType::WasmAnyRef);
handlersResultArea->initResult(currentResultsAreaIndex, loc);
currentResultsAreaIndex++;
currentResultsAreaOffset = loc.endOffset();
// The rest are suspendTagParams, again in reverse order. for (uint32_t i = 0; i < suspendTagParams.length(); i++) {
size_t reverseIndex = suspendTagParams.length() - i - 1;
ValType handlerParam = suspendTagParams[reverseIndex];
// TODO: Not yet implemented.
unimplementedTrap(); returntrue;
}
bool FunctionCompiler::emitGuardSuspending() {
uint32_t tagIndex; if (!iter().readGuardSuspending(&tagIndex)) { returnfalse;
}
if (inDeadCode()) { returntrue;
}
MDefinition* tag = loadTag(tagIndex); if (!tag) { returnfalse;
}
// This will throw an exception (not a trap) which would require us to add // branches to catch blocks. However this is only used in self-hosted wasm // code for JS-PI where we ensure there is no try blocks.
MOZ_ASSERT(!inTryCode());
bool FunctionCompiler::emitBodyExprs() { if (!iter().startFunction(funcIndex())) { returnfalse;
}
#define CHECK(c) \ if (!(c)) returnfalse; \ break
while (true) { if (!mirGen().ensureBallast()) { returnfalse;
}
OpBytes op; if (!iter().readOp(&op)) { returnfalse;
}
switch (op.b0) { case uint16_t(Op::End): if (!emitEnd()) { returnfalse;
} if (iter().controlStackEmpty()) { returntrue;
} break;
// Control opcodes case uint16_t(Op::Unreachable):
CHECK(emitUnreachable()); case uint16_t(Op::Nop):
CHECK(iter().readNop()); case uint16_t(Op::Block):
CHECK(emitBlock()); case uint16_t(Op::Loop):
CHECK(emitLoop()); case uint16_t(Op::If):
CHECK(emitIf()); case uint16_t(Op::Else):
CHECK(emitElse()); case uint16_t(Op::Try):
CHECK(emitTry()); case uint16_t(Op::Catch):
CHECK(emitCatch()); case uint16_t(Op::CatchAll):
CHECK(emitCatchAll()); case uint16_t(Op::Delegate):
CHECK(emitDelegate()); case uint16_t(Op::Throw):
CHECK(emitThrow()); case uint16_t(Op::Rethrow):
CHECK(emitRethrow()); case uint16_t(Op::ThrowRef):
CHECK(emitThrowRef()); case uint16_t(Op::TryTable):
CHECK(emitTryTable()); case uint16_t(Op::Br):
CHECK(emitBr()); case uint16_t(Op::BrIf):
CHECK(emitBrIf()); case uint16_t(Op::BrTable):
CHECK(emitBrTable()); case uint16_t(Op::Return):
CHECK(emitReturn());
// Calls case uint16_t(Op::Call):
CHECK(emitCall(/* asmJSFuncDef = */ false)); case uint16_t(Op::CallIndirect):
CHECK(emitCallIndirect(/* oldStyle = */ false));
// Parametric operators case uint16_t(Op::Drop):
CHECK(iter().readDrop()); case uint16_t(Op::SelectNumeric):
CHECK(emitSelect(/*typed*/ false)); case uint16_t(Op::SelectTyped):
CHECK(emitSelect(/*typed*/ true));
// Locals and globals case uint16_t(Op::LocalGet):
CHECK(emitGetLocal()); case uint16_t(Op::LocalSet):
CHECK(emitSetLocal()); case uint16_t(Op::LocalTee):
CHECK(emitTeeLocal()); case uint16_t(Op::GlobalGet):
CHECK(emitGetGlobal()); case uint16_t(Op::GlobalSet):
CHECK(emitSetGlobal()); case uint16_t(Op::TableGet):
CHECK(emitTableGet()); case uint16_t(Op::TableSet):
CHECK(emitTableSet());
// Memory-related operators case uint16_t(Op::I32Load):
CHECK(emitLoad(ValType::I32, Scalar::Int32)); case uint16_t(Op::I64Load):
CHECK(emitLoad(ValType::I64, Scalar::Int64)); case uint16_t(Op::F32Load):
CHECK(emitLoad(ValType::F32, Scalar::Float32)); case uint16_t(Op::F64Load):
CHECK(emitLoad(ValType::F64, Scalar::Float64)); case uint16_t(Op::I32Load8S):
CHECK(emitLoad(ValType::I32, Scalar::Int8)); case uint16_t(Op::I32Load8U):
CHECK(emitLoad(ValType::I32, Scalar::Uint8)); case uint16_t(Op::I32Load16S):
CHECK(emitLoad(ValType::I32, Scalar::Int16)); case uint16_t(Op::I32Load16U):
CHECK(emitLoad(ValType::I32, Scalar::Uint16)); case uint16_t(Op::I64Load8S):
CHECK(emitLoad(ValType::I64, Scalar::Int8)); case uint16_t(Op::I64Load8U):
CHECK(emitLoad(ValType::I64, Scalar::Uint8)); case uint16_t(Op::I64Load16S):
CHECK(emitLoad(ValType::I64, Scalar::Int16)); case uint16_t(Op::I64Load16U):
CHECK(emitLoad(ValType::I64, Scalar::Uint16)); case uint16_t(Op::I64Load32S):
CHECK(emitLoad(ValType::I64, Scalar::Int32)); case uint16_t(Op::I64Load32U):
CHECK(emitLoad(ValType::I64, Scalar::Uint32)); case uint16_t(Op::I32Store):
CHECK(emitStore(ValType::I32, Scalar::Int32)); case uint16_t(Op::I64Store):
CHECK(emitStore(ValType::I64, Scalar::Int64)); case uint16_t(Op::F32Store):
CHECK(emitStore(ValType::F32, Scalar::Float32)); case uint16_t(Op::F64Store):
CHECK(emitStore(ValType::F64, Scalar::Float64)); case uint16_t(Op::I32Store8):
CHECK(emitStore(ValType::I32, Scalar::Int8)); case uint16_t(Op::I32Store16):
CHECK(emitStore(ValType::I32, Scalar::Int16)); case uint16_t(Op::I64Store8):
CHECK(emitStore(ValType::I64, Scalar::Int8)); case uint16_t(Op::I64Store16):
CHECK(emitStore(ValType::I64, Scalar::Int16)); case uint16_t(Op::I64Store32):
CHECK(emitStore(ValType::I64, Scalar::Int32)); case uint16_t(Op::MemorySize):
CHECK(emitMemorySize()); case uint16_t(Op::MemoryGrow):
CHECK(emitMemoryGrow());
// Constants case uint16_t(Op::I32Const):
CHECK(emitI32Const()); case uint16_t(Op::I64Const):
CHECK(emitI64Const()); case uint16_t(Op::F32Const):
CHECK(emitF32Const()); case uint16_t(Op::F64Const):
CHECK(emitF64Const());
// Comparison operators case uint16_t(Op::I32Eqz):
CHECK(emitConversion<MNot>(ValType::I32, ValType::I32)); case uint16_t(Op::I32Eq):
CHECK(emitComparison(ValType::I32, JSOp::Eq, MCompare::Compare_Int32)); case uint16_t(Op::I32Ne):
CHECK(emitComparison(ValType::I32, JSOp::Ne, MCompare::Compare_Int32)); case uint16_t(Op::I32LtS):
CHECK(emitComparison(ValType::I32, JSOp::Lt, MCompare::Compare_Int32)); case uint16_t(Op::I32LtU):
CHECK(emitComparison(ValType::I32, JSOp::Lt, MCompare::Compare_UInt32)); case uint16_t(Op::I32GtS):
CHECK(emitComparison(ValType::I32, JSOp::Gt, MCompare::Compare_Int32)); case uint16_t(Op::I32GtU):
CHECK(emitComparison(ValType::I32, JSOp::Gt, MCompare::Compare_UInt32)); case uint16_t(Op::I32LeS):
CHECK(emitComparison(ValType::I32, JSOp::Le, MCompare::Compare_Int32)); case uint16_t(Op::I32LeU):
CHECK(emitComparison(ValType::I32, JSOp::Le, MCompare::Compare_UInt32)); case uint16_t(Op::I32GeS):
CHECK(emitComparison(ValType::I32, JSOp::Ge, MCompare::Compare_Int32)); case uint16_t(Op::I32GeU):
CHECK(emitComparison(ValType::I32, JSOp::Ge, MCompare::Compare_UInt32)); case uint16_t(Op::I64Eqz):
CHECK(emitConversion<MNot>(ValType::I64, ValType::I32)); case uint16_t(Op::I64Eq):
CHECK(emitComparison(ValType::I64, JSOp::Eq, MCompare::Compare_Int64)); case uint16_t(Op::I64Ne):
CHECK(emitComparison(ValType::I64, JSOp::Ne, MCompare::Compare_Int64)); case uint16_t(Op::I64LtS):
CHECK(emitComparison(ValType::I64, JSOp::Lt, MCompare::Compare_Int64)); case uint16_t(Op::I64LtU):
CHECK(emitComparison(ValType::I64, JSOp::Lt, MCompare::Compare_UInt64)); case uint16_t(Op::I64GtS):
CHECK(emitComparison(ValType::I64, JSOp::Gt, MCompare::Compare_Int64)); case uint16_t(Op::I64GtU):
CHECK(emitComparison(ValType::I64, JSOp::Gt, MCompare::Compare_UInt64)); case uint16_t(Op::I64LeS):
CHECK(emitComparison(ValType::I64, JSOp::Le, MCompare::Compare_Int64)); case uint16_t(Op::I64LeU):
CHECK(emitComparison(ValType::I64, JSOp::Le, MCompare::Compare_UInt64)); case uint16_t(Op::I64GeS):
CHECK(emitComparison(ValType::I64, JSOp::Ge, MCompare::Compare_Int64)); case uint16_t(Op::I64GeU):
CHECK(emitComparison(ValType::I64, JSOp::Ge, MCompare::Compare_UInt64)); case uint16_t(Op::F32Eq):
CHECK(
emitComparison(ValType::F32, JSOp::Eq, MCompare::Compare_Float32)); case uint16_t(Op::F32Ne):
CHECK(
emitComparison(ValType::F32, JSOp::Ne, MCompare::Compare_Float32)); case uint16_t(Op::F32Lt):
CHECK(
emitComparison(ValType::F32, JSOp::Lt, MCompare::Compare_Float32)); case uint16_t(Op::F32Gt):
CHECK(
emitComparison(ValType::F32, JSOp::Gt, MCompare::Compare_Float32)); case uint16_t(Op::F32Le):
CHECK(
emitComparison(ValType::F32, JSOp::Le, MCompare::Compare_Float32)); case uint16_t(Op::F32Ge):
CHECK(
emitComparison(ValType::F32, JSOp::Ge, MCompare::Compare_Float32)); case uint16_t(Op::F64Eq):
CHECK(emitComparison(ValType::F64, JSOp::Eq, MCompare::Compare_Double)); case uint16_t(Op::F64Ne):
CHECK(emitComparison(ValType::F64, JSOp::Ne, MCompare::Compare_Double)); case uint16_t(Op::F64Lt):
CHECK(emitComparison(ValType::F64, JSOp::Lt, MCompare::Compare_Double)); case uint16_t(Op::F64Gt):
CHECK(emitComparison(ValType::F64, JSOp::Gt, MCompare::Compare_Double)); case uint16_t(Op::F64Le):
CHECK(emitComparison(ValType::F64, JSOp::Le, MCompare::Compare_Double)); case uint16_t(Op::F64Ge):
CHECK(emitComparison(ValType::F64, JSOp::Ge, MCompare::Compare_Double));
// Numeric operators case uint16_t(Op::I32Clz):
CHECK(emitUnaryWithType<MClz>(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32Ctz):
CHECK(emitUnaryWithType<MCtz>(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32Popcnt):
CHECK(emitUnaryWithType<MPopcnt>(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32Add):
CHECK(emitAdd(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32Sub):
CHECK(emitSub(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32Mul):
CHECK(emitMul(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32DivS): case uint16_t(Op::I32DivU):
CHECK(emitDiv(ValType::I32, MIRType::Int32, Op(op.b0) == Op::I32DivU)); case uint16_t(Op::I32RemS): case uint16_t(Op::I32RemU):
CHECK(emitRem(ValType::I32, MIRType::Int32, Op(op.b0) == Op::I32RemU)); case uint16_t(Op::I32And):
CHECK(emitBitwiseAndOrXor(ValType::I32, MIRType::Int32,
MWasmBinaryBitwise::SubOpcode::And)); case uint16_t(Op::I32Or):
CHECK(emitBitwiseAndOrXor(ValType::I32, MIRType::Int32,
MWasmBinaryBitwise::SubOpcode::Or)); case uint16_t(Op::I32Xor):
CHECK(emitBitwiseAndOrXor(ValType::I32, MIRType::Int32,
MWasmBinaryBitwise::SubOpcode::Xor)); case uint16_t(Op::I32Shl):
CHECK(emitShift<MLsh>(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32ShrS):
CHECK(emitShift<MRsh>(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32ShrU):
CHECK(emitUrsh(ValType::I32, MIRType::Int32)); case uint16_t(Op::I32Rotl): case uint16_t(Op::I32Rotr):
CHECK(emitRotate(ValType::I32, Op(op.b0) == Op::I32Rotl)); case uint16_t(Op::I64Clz):
CHECK(emitUnaryWithType<MClz>(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64Ctz):
CHECK(emitUnaryWithType<MCtz>(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64Popcnt):
CHECK(emitUnaryWithType<MPopcnt>(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64Add):
CHECK(emitAdd(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64Sub):
CHECK(emitSub(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64Mul):
CHECK(emitMul(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64DivS): case uint16_t(Op::I64DivU):
CHECK(emitDiv(ValType::I64, MIRType::Int64, Op(op.b0) == Op::I64DivU)); case uint16_t(Op::I64RemS): case uint16_t(Op::I64RemU):
CHECK(emitRem(ValType::I64, MIRType::Int64, Op(op.b0) == Op::I64RemU)); case uint16_t(Op::I64And):
CHECK(emitBitwiseAndOrXor(ValType::I64, MIRType::Int64,
MWasmBinaryBitwise::SubOpcode::And)); case uint16_t(Op::I64Or):
CHECK(emitBitwiseAndOrXor(ValType::I64, MIRType::Int64,
MWasmBinaryBitwise::SubOpcode::Or)); case uint16_t(Op::I64Xor):
CHECK(emitBitwiseAndOrXor(ValType::I64, MIRType::Int64,
MWasmBinaryBitwise::SubOpcode::Xor)); case uint16_t(Op::I64Shl):
CHECK(emitShift<MLsh>(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64ShrS):
CHECK(emitShift<MRsh>(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64ShrU):
CHECK(emitUrsh(ValType::I64, MIRType::Int64)); case uint16_t(Op::I64Rotl): case uint16_t(Op::I64Rotr):
CHECK(emitRotate(ValType::I64, Op(op.b0) == Op::I64Rotl)); case uint16_t(Op::F32Abs):
CHECK(emitUnaryWithType<MAbs>(ValType::F32, MIRType::Float32)); case uint16_t(Op::F32Neg):
CHECK(emitUnaryWithType<MWasmNeg>(ValType::F32, MIRType::Float32)); case uint16_t(Op::F32Ceil):
CHECK(emitUnaryMathBuiltinCall(SASigCeilF)); case uint16_t(Op::F32Floor):
CHECK(emitUnaryMathBuiltinCall(SASigFloorF)); case uint16_t(Op::F32Trunc):
CHECK(emitUnaryMathBuiltinCall(SASigTruncF)); case uint16_t(Op::F32Nearest):
CHECK(emitUnaryMathBuiltinCall(SASigNearbyIntF)); case uint16_t(Op::F32Sqrt):
CHECK(emitUnaryWithType<MSqrt>(ValType::F32, MIRType::Float32)); case uint16_t(Op::F32Add):
CHECK(emitAdd(ValType::F32, MIRType::Float32)); case uint16_t(Op::F32Sub):
CHECK(emitSub(ValType::F32, MIRType::Float32)); case uint16_t(Op::F32Mul):
CHECK(emitMul(ValType::F32, MIRType::Float32)); case uint16_t(Op::F32Div):
CHECK(emitDiv(ValType::F32, MIRType::Float32, /* isUnsigned = */ false)); case uint16_t(Op::F32Min): case uint16_t(Op::F32Max):
CHECK(emitMinMax(ValType::F32, MIRType::Float32,
Op(op.b0) == Op::F32Max)); case uint16_t(Op::F32CopySign):
CHECK(emitCopySign(ValType::F32)); case uint16_t(Op::F64Abs):
CHECK(emitUnaryWithType<MAbs>(ValType::F64, MIRType::Double)); case uint16_t(Op::F64Neg):
CHECK(emitUnaryWithType<MWasmNeg>(ValType::F64, MIRType::Double)); case uint16_t(Op::F64Ceil):
CHECK(emitUnaryMathBuiltinCall(SASigCeilD)); case uint16_t(Op::F64Floor):
CHECK(emitUnaryMathBuiltinCall(SASigFloorD)); case uint16_t(Op::F64Trunc):
CHECK(emitUnaryMathBuiltinCall(SASigTruncD)); case uint16_t(Op::F64Nearest):
CHECK(emitUnaryMathBuiltinCall(SASigNearbyIntD)); case uint16_t(Op::F64Sqrt):
CHECK(emitUnaryWithType<MSqrt>(ValType::F64, MIRType::Double)); case uint16_t(Op::F64Add):
CHECK(emitAdd(ValType::F64, MIRType::Double)); case uint16_t(Op::F64Sub):
CHECK(emitSub(ValType::F64, MIRType::Double)); case uint16_t(Op::F64Mul):
CHECK(emitMul(ValType::F64, MIRType::Double)); case uint16_t(Op::F64Div):
CHECK(emitDiv(ValType::F64, MIRType::Double, /* isUnsigned = */ false)); case uint16_t(Op::F64Min): case uint16_t(Op::F64Max):
CHECK(
emitMinMax(ValType::F64, MIRType::Double, Op(op.b0) == Op::F64Max)); case uint16_t(Op::F64CopySign):
CHECK(emitCopySign(ValType::F64));
// Conversions case uint16_t(Op::I32WrapI64):
CHECK(emitWrapI32()); case uint16_t(Op::I32TruncF32S): case uint16_t(Op::I32TruncF32U):
CHECK(emitTruncate(ValType::F32, ValType::I32,
Op(op.b0) == Op::I32TruncF32U, false)); case uint16_t(Op::I32TruncF64S): case uint16_t(Op::I32TruncF64U):
CHECK(emitTruncate(ValType::F64, ValType::I32,
Op(op.b0) == Op::I32TruncF64U, false)); case uint16_t(Op::I64ExtendI32S): case uint16_t(Op::I64ExtendI32U):
CHECK(emitExtendI32(Op(op.b0) == Op::I64ExtendI32U)); case uint16_t(Op::I64TruncF32S): case uint16_t(Op::I64TruncF32U):
CHECK(emitTruncate(ValType::F32, ValType::I64,
Op(op.b0) == Op::I64TruncF32U, false)); case uint16_t(Op::I64TruncF64S): case uint16_t(Op::I64TruncF64U):
CHECK(emitTruncate(ValType::F64, ValType::I64,
Op(op.b0) == Op::I64TruncF64U, false)); case uint16_t(Op::F32ConvertI32S):
CHECK(emitConversion<MToFloat32>(ValType::I32, ValType::F32)); case uint16_t(Op::F32ConvertI32U):
CHECK(
emitConversion<MWasmUnsignedToFloat32>(ValType::I32, ValType::F32)); case uint16_t(Op::F32ConvertI64S): case uint16_t(Op::F32ConvertI64U):
CHECK(emitConvertI64ToFloatingPoint(ValType::F32, MIRType::Float32,
Op(op.b0) == Op::F32ConvertI64U)); case uint16_t(Op::F32DemoteF64):
CHECK(emitConversion<MToFloat32>(ValType::F64, ValType::F32)); case uint16_t(Op::F64ConvertI32S):
CHECK(emitConversion<MToDouble>(ValType::I32, ValType::F64)); case uint16_t(Op::F64ConvertI32U):
CHECK(
emitConversion<MWasmUnsignedToDouble>(ValType::I32, ValType::F64)); case uint16_t(Op::F64ConvertI64S): case uint16_t(Op::F64ConvertI64U):
CHECK(emitConvertI64ToFloatingPoint(ValType::F64, MIRType::Double,
Op(op.b0) == Op::F64ConvertI64U)); case uint16_t(Op::F64PromoteF32):
CHECK(emitConversion<MToDouble>(ValType::F32, ValType::F64));
// Reinterpretations case uint16_t(Op::I32ReinterpretF32):
CHECK(emitReinterpret(ValType::I32, ValType::F32, MIRType::Int32)); case uint16_t(Op::I64ReinterpretF64):
CHECK(emitReinterpret(ValType::I64, ValType::F64, MIRType::Int64)); case uint16_t(Op::F32ReinterpretI32):
CHECK(emitReinterpret(ValType::F32, ValType::I32, MIRType::Float32)); case uint16_t(Op::F64ReinterpretI64):
CHECK(emitReinterpret(ValType::F64, ValType::I64, MIRType::Double));
case uint16_t(Op::RefEq):
CHECK(emitComparison(RefType::eq(), JSOp::Eq,
MCompare::Compare_WasmAnyRef)); case uint16_t(Op::RefFunc):
CHECK(emitRefFunc()); case uint16_t(Op::RefNull):
CHECK(emitRefNull()); case uint16_t(Op::RefIsNull):
CHECK(emitRefIsNull());
// Sign extensions case uint16_t(Op::I32Extend8S):
CHECK(emitSignExtend(1, 4)); case uint16_t(Op::I32Extend16S):
CHECK(emitSignExtend(2, 4)); case uint16_t(Op::I64Extend8S):
CHECK(emitSignExtend(1, 8)); case uint16_t(Op::I64Extend16S):
CHECK(emitSignExtend(2, 8)); case uint16_t(Op::I64Extend32S):
CHECK(emitSignExtend(4, 8));
case uint16_t(Op::ReturnCall): {
CHECK(emitReturnCall());
} case uint16_t(Op::ReturnCallIndirect): {
CHECK(emitReturnCallIndirect());
}
case uint16_t(Op::RefAsNonNull):
CHECK(emitRefAsNonNull()); case uint16_t(Op::BrOnNull): {
CHECK(emitBrOnNull());
} case uint16_t(Op::BrOnNonNull): {
CHECK(emitBrOnNonNull());
} case uint16_t(Op::CallRef): {
CHECK(emitCallRef());
}
case uint16_t(Op::ReturnCallRef): {
CHECK(emitReturnCallRef());
}
#ifdef ENABLE_WASM_JSPI case uint16_t(Op::ContNew): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitContNew());
} case uint16_t(Op::ContBind): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitContBind());
} case uint16_t(Op::Suspend): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitSuspend());
} case uint16_t(Op::Resume): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitResume());
} case uint16_t(Op::ResumeThrow): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitResumeThrow());
} case uint16_t(Op::ResumeThrowRef): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitResumeThrowRef());
} case uint16_t(Op::Switch): { if (!codeMeta().stackSwitchingEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitSwitch());
} #endif// ENABLE_WASM_JSPI
// Gc operations case uint16_t(Op::GcPrefix): { switch (op.b1) { case uint32_t(GcOp::StructNew):
CHECK(emitStructNew()); case uint32_t(GcOp::StructNewDefault):
CHECK(emitStructNewDefault()); case uint32_t(GcOp::StructSet):
CHECK(emitStructSet()); case uint32_t(GcOp::StructGet):
CHECK(emitStructGet(FieldWideningOp::None)); case uint32_t(GcOp::StructGetS):
CHECK(emitStructGet(FieldWideningOp::Signed)); case uint32_t(GcOp::StructGetU):
CHECK(emitStructGet(FieldWideningOp::Unsigned)); case uint32_t(GcOp::ArrayNew):
CHECK(emitArrayNew()); case uint32_t(GcOp::ArrayNewDefault):
CHECK(emitArrayNewDefault()); case uint32_t(GcOp::ArrayNewFixed):
CHECK(emitArrayNewFixed()); case uint32_t(GcOp::ArrayNewData):
CHECK(emitArrayNewData()); case uint32_t(GcOp::ArrayNewElem):
CHECK(emitArrayNewElem()); case uint32_t(GcOp::ArrayInitData):
CHECK(emitArrayInitData()); case uint32_t(GcOp::ArrayInitElem):
CHECK(emitArrayInitElem()); case uint32_t(GcOp::ArraySet):
CHECK(emitArraySet()); case uint32_t(GcOp::ArrayGet):
CHECK(emitArrayGet(FieldWideningOp::None)); case uint32_t(GcOp::ArrayGetS):
CHECK(emitArrayGet(FieldWideningOp::Signed)); case uint32_t(GcOp::ArrayGetU):
CHECK(emitArrayGet(FieldWideningOp::Unsigned)); case uint32_t(GcOp::ArrayLen):
CHECK(emitArrayLen()); case uint32_t(GcOp::ArrayCopy):
CHECK(emitArrayCopy()); case uint32_t(GcOp::ArrayFill):
CHECK(emitArrayFill()); case uint32_t(GcOp::RefI31):
CHECK(emitRefI31()); case uint32_t(GcOp::I31GetS):
CHECK(emitI31Get(FieldWideningOp::Signed)); case uint32_t(GcOp::I31GetU):
CHECK(emitI31Get(FieldWideningOp::Unsigned)); case uint32_t(GcOp::BrOnCast):
CHECK(emitBrOnCast(/*onSuccess=*/true)); case uint32_t(GcOp::BrOnCastFail):
CHECK(emitBrOnCast(/*onSuccess=*/false)); case uint32_t(GcOp::RefTest):
CHECK(emitRefTest(/*nullable=*/false)); case uint32_t(GcOp::RefTestNull):
CHECK(emitRefTest(/*nullable=*/true)); case uint32_t(GcOp::RefCast):
CHECK(emitRefCast(/*nullable=*/false)); case uint32_t(GcOp::RefCastNull):
CHECK(emitRefCast(/*nullable=*/true)); case uint16_t(GcOp::AnyConvertExtern):
CHECK(emitAnyConvertExtern()); case uint16_t(GcOp::ExternConvertAny):
CHECK(emitExternConvertAny()); default: return iter().unrecognizedOpcode(&op);
} // switch (op.b1) break;
}
// SIMD operations #ifdef ENABLE_WASM_SIMD case uint16_t(Op::SimdPrefix): { if (!codeMeta().simdAvailable()) { return iter().unrecognizedOpcode(&op);
} switch (op.b1) { case uint32_t(SimdOp::V128Const):
CHECK(emitConstSimd128()); case uint32_t(SimdOp::V128Load):
CHECK(emitLoad(ValType::V128, Scalar::Simd128)); case uint32_t(SimdOp::V128Store):
CHECK(emitStore(ValType::V128, Scalar::Simd128)); case uint32_t(SimdOp::V128And): case uint32_t(SimdOp::V128Or): case uint32_t(SimdOp::V128Xor): case uint32_t(SimdOp::I8x16AvgrU): case uint32_t(SimdOp::I16x8AvgrU): case uint32_t(SimdOp::I8x16Add): case uint32_t(SimdOp::I8x16AddSatS): case uint32_t(SimdOp::I8x16AddSatU): case uint32_t(SimdOp::I8x16MinS): case uint32_t(SimdOp::I8x16MinU): case uint32_t(SimdOp::I8x16MaxS): case uint32_t(SimdOp::I8x16MaxU): case uint32_t(SimdOp::I16x8Add): case uint32_t(SimdOp::I16x8AddSatS): case uint32_t(SimdOp::I16x8AddSatU): case uint32_t(SimdOp::I16x8Mul): case uint32_t(SimdOp::I16x8MinS): case uint32_t(SimdOp::I16x8MinU): case uint32_t(SimdOp::I16x8MaxS): case uint32_t(SimdOp::I16x8MaxU): case uint32_t(SimdOp::I32x4Add): case uint32_t(SimdOp::I32x4Mul): case uint32_t(SimdOp::I32x4MinS): case uint32_t(SimdOp::I32x4MinU): case uint32_t(SimdOp::I32x4MaxS): case uint32_t(SimdOp::I32x4MaxU): case uint32_t(SimdOp::I64x2Add): case uint32_t(SimdOp::I64x2Mul): case uint32_t(SimdOp::F32x4Add): case uint32_t(SimdOp::F32x4Mul): case uint32_t(SimdOp::F32x4Min): case uint32_t(SimdOp::F32x4Max): case uint32_t(SimdOp::F64x2Add): case uint32_t(SimdOp::F64x2Mul): case uint32_t(SimdOp::F64x2Min): case uint32_t(SimdOp::F64x2Max): case uint32_t(SimdOp::I8x16Eq): case uint32_t(SimdOp::I8x16Ne): case uint32_t(SimdOp::I16x8Eq): case uint32_t(SimdOp::I16x8Ne): case uint32_t(SimdOp::I32x4Eq): case uint32_t(SimdOp::I32x4Ne): case uint32_t(SimdOp::I64x2Eq): case uint32_t(SimdOp::I64x2Ne): case uint32_t(SimdOp::F32x4Eq): case uint32_t(SimdOp::F32x4Ne): case uint32_t(SimdOp::F64x2Eq): case uint32_t(SimdOp::F64x2Ne): case uint32_t(SimdOp::I32x4DotI16x8S): case uint32_t(SimdOp::I16x8ExtmulLowI8x16S): case uint32_t(SimdOp::I16x8ExtmulHighI8x16S): case uint32_t(SimdOp::I16x8ExtmulLowI8x16U): case uint32_t(SimdOp::I16x8ExtmulHighI8x16U): case uint32_t(SimdOp::I32x4ExtmulLowI16x8S): case uint32_t(SimdOp::I32x4ExtmulHighI16x8S): case uint32_t(SimdOp::I32x4ExtmulLowI16x8U): case uint32_t(SimdOp::I32x4ExtmulHighI16x8U): case uint32_t(SimdOp::I64x2ExtmulLowI32x4S): case uint32_t(SimdOp::I64x2ExtmulHighI32x4S): case uint32_t(SimdOp::I64x2ExtmulLowI32x4U): case uint32_t(SimdOp::I64x2ExtmulHighI32x4U): case uint32_t(SimdOp::I16x8Q15MulrSatS):
CHECK(emitBinarySimd128(/* commutative= */ true, SimdOp(op.b1))); case uint32_t(SimdOp::V128AndNot): case uint32_t(SimdOp::I8x16Sub): case uint32_t(SimdOp::I8x16SubSatS): case uint32_t(SimdOp::I8x16SubSatU): case uint32_t(SimdOp::I16x8Sub): case uint32_t(SimdOp::I16x8SubSatS): case uint32_t(SimdOp::I16x8SubSatU): case uint32_t(SimdOp::I32x4Sub): case uint32_t(SimdOp::I64x2Sub): case uint32_t(SimdOp::F32x4Sub): case uint32_t(SimdOp::F32x4Div): case uint32_t(SimdOp::F64x2Sub): case uint32_t(SimdOp::F64x2Div): case uint32_t(SimdOp::I8x16NarrowI16x8S): case uint32_t(SimdOp::I8x16NarrowI16x8U): case uint32_t(SimdOp::I16x8NarrowI32x4S): case uint32_t(SimdOp::I16x8NarrowI32x4U): case uint32_t(SimdOp::I8x16LtS): case uint32_t(SimdOp::I8x16LtU): case uint32_t(SimdOp::I8x16GtS): case uint32_t(SimdOp::I8x16GtU): case uint32_t(SimdOp::I8x16LeS): case uint32_t(SimdOp::I8x16LeU): case uint32_t(SimdOp::I8x16GeS): case uint32_t(SimdOp::I8x16GeU): case uint32_t(SimdOp::I16x8LtS): case uint32_t(SimdOp::I16x8LtU): case uint32_t(SimdOp::I16x8GtS): case uint32_t(SimdOp::I16x8GtU): case uint32_t(SimdOp::I16x8LeS): case uint32_t(SimdOp::I16x8LeU): case uint32_t(SimdOp::I16x8GeS): case uint32_t(SimdOp::I16x8GeU): case uint32_t(SimdOp::I32x4LtS): case uint32_t(SimdOp::I32x4LtU): case uint32_t(SimdOp::I32x4GtS): case uint32_t(SimdOp::I32x4GtU): case uint32_t(SimdOp::I32x4LeS): case uint32_t(SimdOp::I32x4LeU): case uint32_t(SimdOp::I32x4GeS): case uint32_t(SimdOp::I32x4GeU): case uint32_t(SimdOp::I64x2LtS): case uint32_t(SimdOp::I64x2GtS): case uint32_t(SimdOp::I64x2LeS): case uint32_t(SimdOp::I64x2GeS): case uint32_t(SimdOp::F32x4Lt): case uint32_t(SimdOp::F32x4Gt): case uint32_t(SimdOp::F32x4Le): case uint32_t(SimdOp::F32x4Ge): case uint32_t(SimdOp::F64x2Lt): case uint32_t(SimdOp::F64x2Gt): case uint32_t(SimdOp::F64x2Le): case uint32_t(SimdOp::F64x2Ge): case uint32_t(SimdOp::I8x16Swizzle): case uint32_t(SimdOp::F32x4PMax): case uint32_t(SimdOp::F32x4PMin): case uint32_t(SimdOp::F64x2PMax): case uint32_t(SimdOp::F64x2PMin):
CHECK(emitBinarySimd128(/* commutative= */ false, SimdOp(op.b1))); case uint32_t(SimdOp::I8x16Splat): case uint32_t(SimdOp::I16x8Splat): case uint32_t(SimdOp::I32x4Splat):
CHECK(emitSplatSimd128(ValType::I32, SimdOp(op.b1))); case uint32_t(SimdOp::I64x2Splat):
CHECK(emitSplatSimd128(ValType::I64, SimdOp(op.b1))); case uint32_t(SimdOp::F32x4Splat):
CHECK(emitSplatSimd128(ValType::F32, SimdOp(op.b1))); case uint32_t(SimdOp::F64x2Splat):
CHECK(emitSplatSimd128(ValType::F64, SimdOp(op.b1))); case uint32_t(SimdOp::I8x16Neg): case uint32_t(SimdOp::I16x8Neg): case uint32_t(SimdOp::I16x8ExtendLowI8x16S): case uint32_t(SimdOp::I16x8ExtendHighI8x16S): case uint32_t(SimdOp::I16x8ExtendLowI8x16U): case uint32_t(SimdOp::I16x8ExtendHighI8x16U): case uint32_t(SimdOp::I32x4Neg): case uint32_t(SimdOp::I32x4ExtendLowI16x8S): case uint32_t(SimdOp::I32x4ExtendHighI16x8S): case uint32_t(SimdOp::I32x4ExtendLowI16x8U): case uint32_t(SimdOp::I32x4ExtendHighI16x8U): case uint32_t(SimdOp::I32x4TruncSatF32x4S): case uint32_t(SimdOp::I32x4TruncSatF32x4U): case uint32_t(SimdOp::I64x2Neg): case uint32_t(SimdOp::I64x2ExtendLowI32x4S): case uint32_t(SimdOp::I64x2ExtendHighI32x4S): case uint32_t(SimdOp::I64x2ExtendLowI32x4U): case uint32_t(SimdOp::I64x2ExtendHighI32x4U): case uint32_t(SimdOp::F32x4Abs): case uint32_t(SimdOp::F32x4Neg): case uint32_t(SimdOp::F32x4Sqrt): case uint32_t(SimdOp::F32x4ConvertI32x4S): case uint32_t(SimdOp::F32x4ConvertI32x4U): case uint32_t(SimdOp::F64x2Abs): case uint32_t(SimdOp::F64x2Neg): case uint32_t(SimdOp::F64x2Sqrt): case uint32_t(SimdOp::V128Not): case uint32_t(SimdOp::I8x16Popcnt): case uint32_t(SimdOp::I8x16Abs): case uint32_t(SimdOp::I16x8Abs): case uint32_t(SimdOp::I32x4Abs): case uint32_t(SimdOp::I64x2Abs): case uint32_t(SimdOp::F32x4Ceil): case uint32_t(SimdOp::F32x4Floor): case uint32_t(SimdOp::F32x4Trunc): case uint32_t(SimdOp::F32x4Nearest): case uint32_t(SimdOp::F64x2Ceil): case uint32_t(SimdOp::F64x2Floor): case uint32_t(SimdOp::F64x2Trunc): case uint32_t(SimdOp::F64x2Nearest): case uint32_t(SimdOp::F32x4DemoteF64x2Zero): case uint32_t(SimdOp::F64x2PromoteLowF32x4): case uint32_t(SimdOp::F64x2ConvertLowI32x4S): case uint32_t(SimdOp::F64x2ConvertLowI32x4U): case uint32_t(SimdOp::I32x4TruncSatF64x2SZero): case uint32_t(SimdOp::I32x4TruncSatF64x2UZero): case uint32_t(SimdOp::I16x8ExtaddPairwiseI8x16S): case uint32_t(SimdOp::I16x8ExtaddPairwiseI8x16U): case uint32_t(SimdOp::I32x4ExtaddPairwiseI16x8S): case uint32_t(SimdOp::I32x4ExtaddPairwiseI16x8U):
CHECK(emitUnarySimd128(SimdOp(op.b1))); case uint32_t(SimdOp::V128AnyTrue): case uint32_t(SimdOp::I8x16AllTrue): case uint32_t(SimdOp::I16x8AllTrue): case uint32_t(SimdOp::I32x4AllTrue): case uint32_t(SimdOp::I64x2AllTrue): case uint32_t(SimdOp::I8x16Bitmask): case uint32_t(SimdOp::I16x8Bitmask): case uint32_t(SimdOp::I32x4Bitmask): case uint32_t(SimdOp::I64x2Bitmask):
CHECK(emitReduceSimd128(SimdOp(op.b1))); case uint32_t(SimdOp::I8x16Shl): case uint32_t(SimdOp::I8x16ShrS): case uint32_t(SimdOp::I8x16ShrU): case uint32_t(SimdOp::I16x8Shl): case uint32_t(SimdOp::I16x8ShrS): case uint32_t(SimdOp::I16x8ShrU): case uint32_t(SimdOp::I32x4Shl): case uint32_t(SimdOp::I32x4ShrS): case uint32_t(SimdOp::I32x4ShrU): case uint32_t(SimdOp::I64x2Shl): case uint32_t(SimdOp::I64x2ShrS): case uint32_t(SimdOp::I64x2ShrU):
CHECK(emitShiftSimd128(SimdOp(op.b1))); case uint32_t(SimdOp::I8x16ExtractLaneS): case uint32_t(SimdOp::I8x16ExtractLaneU):
CHECK(emitExtractLaneSimd128(ValType::I32, 16, SimdOp(op.b1))); case uint32_t(SimdOp::I16x8ExtractLaneS): case uint32_t(SimdOp::I16x8ExtractLaneU):
CHECK(emitExtractLaneSimd128(ValType::I32, 8, SimdOp(op.b1))); case uint32_t(SimdOp::I32x4ExtractLane):
CHECK(emitExtractLaneSimd128(ValType::I32, 4, SimdOp(op.b1))); case uint32_t(SimdOp::I64x2ExtractLane):
CHECK(emitExtractLaneSimd128(ValType::I64, 2, SimdOp(op.b1))); case uint32_t(SimdOp::F32x4ExtractLane):
CHECK(emitExtractLaneSimd128(ValType::F32, 4, SimdOp(op.b1))); case uint32_t(SimdOp::F64x2ExtractLane):
CHECK(emitExtractLaneSimd128(ValType::F64, 2, SimdOp(op.b1))); case uint32_t(SimdOp::I8x16ReplaceLane):
CHECK(emitReplaceLaneSimd128(ValType::I32, 16, SimdOp(op.b1))); case uint32_t(SimdOp::I16x8ReplaceLane):
CHECK(emitReplaceLaneSimd128(ValType::I32, 8, SimdOp(op.b1))); case uint32_t(SimdOp::I32x4ReplaceLane):
CHECK(emitReplaceLaneSimd128(ValType::I32, 4, SimdOp(op.b1))); case uint32_t(SimdOp::I64x2ReplaceLane):
CHECK(emitReplaceLaneSimd128(ValType::I64, 2, SimdOp(op.b1))); case uint32_t(SimdOp::F32x4ReplaceLane):
CHECK(emitReplaceLaneSimd128(ValType::F32, 4, SimdOp(op.b1))); case uint32_t(SimdOp::F64x2ReplaceLane):
CHECK(emitReplaceLaneSimd128(ValType::F64, 2, SimdOp(op.b1))); case uint32_t(SimdOp::V128Bitselect):
CHECK(emitTernarySimd128(SimdOp(op.b1))); case uint32_t(SimdOp::I8x16Shuffle):
CHECK(emitShuffleSimd128()); case uint32_t(SimdOp::V128Load8Splat):
CHECK(emitLoadSplatSimd128(Scalar::Uint8, SimdOp::I8x16Splat)); case uint32_t(SimdOp::V128Load16Splat):
CHECK(emitLoadSplatSimd128(Scalar::Uint16, SimdOp::I16x8Splat)); case uint32_t(SimdOp::V128Load32Splat):
CHECK(emitLoadSplatSimd128(Scalar::Float32, SimdOp::I32x4Splat)); case uint32_t(SimdOp::V128Load64Splat):
CHECK(emitLoadSplatSimd128(Scalar::Float64, SimdOp::I64x2Splat)); case uint32_t(SimdOp::V128Load8x8S): case uint32_t(SimdOp::V128Load8x8U): case uint32_t(SimdOp::V128Load16x4S): case uint32_t(SimdOp::V128Load16x4U): case uint32_t(SimdOp::V128Load32x2S): case uint32_t(SimdOp::V128Load32x2U):
CHECK(emitLoadExtendSimd128(SimdOp(op.b1))); case uint32_t(SimdOp::V128Load32Zero):
CHECK(emitLoadZeroSimd128(Scalar::Float32, 4)); case uint32_t(SimdOp::V128Load64Zero):
CHECK(emitLoadZeroSimd128(Scalar::Float64, 8)); case uint32_t(SimdOp::V128Load8Lane):
CHECK(emitLoadLaneSimd128(1)); case uint32_t(SimdOp::V128Load16Lane):
CHECK(emitLoadLaneSimd128(2)); case uint32_t(SimdOp::V128Load32Lane):
CHECK(emitLoadLaneSimd128(4)); case uint32_t(SimdOp::V128Load64Lane):
CHECK(emitLoadLaneSimd128(8)); case uint32_t(SimdOp::V128Store8Lane):
CHECK(emitStoreLaneSimd128(1)); case uint32_t(SimdOp::V128Store16Lane):
CHECK(emitStoreLaneSimd128(2)); case uint32_t(SimdOp::V128Store32Lane):
CHECK(emitStoreLaneSimd128(4)); case uint32_t(SimdOp::V128Store64Lane):
CHECK(emitStoreLaneSimd128(8)); # ifdef ENABLE_WASM_RELAXED_SIMD case uint32_t(SimdOp::F32x4RelaxedMadd): case uint32_t(SimdOp::F32x4RelaxedNmadd): case uint32_t(SimdOp::F64x2RelaxedMadd): case uint32_t(SimdOp::F64x2RelaxedNmadd): case uint32_t(SimdOp::I8x16RelaxedLaneSelect): case uint32_t(SimdOp::I16x8RelaxedLaneSelect): case uint32_t(SimdOp::I32x4RelaxedLaneSelect): case uint32_t(SimdOp::I64x2RelaxedLaneSelect): case uint32_t(SimdOp::I32x4RelaxedDotI8x16I7x16AddS): { if (!codeMeta().v128RelaxedEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitTernarySimd128(SimdOp(op.b1)));
} case uint32_t(SimdOp::F32x4RelaxedMin): case uint32_t(SimdOp::F32x4RelaxedMax): case uint32_t(SimdOp::F64x2RelaxedMin): case uint32_t(SimdOp::F64x2RelaxedMax): { if (!codeMeta().v128RelaxedEnabled()) { return iter().unrecognizedOpcode(&op);
} // These aren't really commutative, because at least on Intel, the // behaviour in the presence of NaNs depends on the order of the // operands. And we need to have that ordering fixed, so that we // can produce the same results as baseline. See bug 1946618.
CHECK(emitBinarySimd128(/* commutative= */ false, SimdOp(op.b1)));
} case uint32_t(SimdOp::I16x8RelaxedQ15MulrS): { if (!codeMeta().v128RelaxedEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitBinarySimd128(/* commutative= */ true, SimdOp(op.b1)));
} case uint32_t(SimdOp::I32x4RelaxedTruncF32x4S): case uint32_t(SimdOp::I32x4RelaxedTruncF32x4U): case uint32_t(SimdOp::I32x4RelaxedTruncF64x2SZero): case uint32_t(SimdOp::I32x4RelaxedTruncF64x2UZero): { if (!codeMeta().v128RelaxedEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitUnarySimd128(SimdOp(op.b1)));
} case uint32_t(SimdOp::I8x16RelaxedSwizzle): case uint32_t(SimdOp::I16x8RelaxedDotI8x16I7x16S): { if (!codeMeta().v128RelaxedEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitBinarySimd128(/* commutative= */ false, SimdOp(op.b1)));
} # endif
// Miscellaneous operations case uint16_t(Op::MiscPrefix): { switch (op.b1) { case uint32_t(MiscOp::I32TruncSatF32S): case uint32_t(MiscOp::I32TruncSatF32U):
CHECK(emitTruncate(ValType::F32, ValType::I32,
MiscOp(op.b1) == MiscOp::I32TruncSatF32U, true)); case uint32_t(MiscOp::I32TruncSatF64S): case uint32_t(MiscOp::I32TruncSatF64U):
CHECK(emitTruncate(ValType::F64, ValType::I32,
MiscOp(op.b1) == MiscOp::I32TruncSatF64U, true)); case uint32_t(MiscOp::I64TruncSatF32S): case uint32_t(MiscOp::I64TruncSatF32U):
CHECK(emitTruncate(ValType::F32, ValType::I64,
MiscOp(op.b1) == MiscOp::I64TruncSatF32U, true)); case uint32_t(MiscOp::I64TruncSatF64S): case uint32_t(MiscOp::I64TruncSatF64U):
CHECK(emitTruncate(ValType::F64, ValType::I64,
MiscOp(op.b1) == MiscOp::I64TruncSatF64U, true)); case uint32_t(MiscOp::MemoryCopy):
CHECK(emitMemCopy()); case uint32_t(MiscOp::DataDrop):
CHECK(emitDataOrElemDrop(/*isData=*/true)); case uint32_t(MiscOp::MemoryFill):
CHECK(emitMemFill()); case uint32_t(MiscOp::MemoryInit):
CHECK(emitMemInit()); case uint32_t(MiscOp::TableCopy):
CHECK(emitTableCopy()); case uint32_t(MiscOp::ElemDrop):
CHECK(emitDataOrElemDrop(/*isData=*/false)); case uint32_t(MiscOp::TableInit):
CHECK(emitTableInit()); case uint32_t(MiscOp::TableFill):
CHECK(emitTableFill()); #if ENABLE_WASM_MEMORY_CONTROL case uint32_t(MiscOp::MemoryDiscard): { if (!codeMeta().memoryControlEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitMemDiscard());
} #endif case uint32_t(MiscOp::TableGrow):
CHECK(emitTableGrow()); case uint32_t(MiscOp::TableSize):
CHECK(emitTableSize());
case uint32_t(MiscOp::I64Add128): if (!codeMeta().wideArithmeticEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitI64AddSub128(/*isAdd=*/true)); case uint32_t(MiscOp::I64Sub128): if (!codeMeta().wideArithmeticEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitI64AddSub128(/*isAdd=*/false)); case uint32_t(MiscOp::I64MulWideS): if (!codeMeta().wideArithmeticEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitI64MulWide(/*isSigned=*/true)); case uint32_t(MiscOp::I64MulWideU): if (!codeMeta().wideArithmeticEnabled()) { return iter().unrecognizedOpcode(&op);
}
CHECK(emitI64MulWide(/*isSigned=*/false));
// Thread operations case uint16_t(Op::ThreadPrefix): { // Though thread ops can be used on nonshared memories, we make them // unavailable if shared memory has been disabled in the prefs, for // maximum predictability and safety and consistency with JS. if (codeMeta().sharedMemoryEnabled() == Shareable::False) { return iter().unrecognizedOpcode(&op);
} switch (op.b1) { case uint32_t(ThreadOp::Notify):
CHECK(emitNotify());
case uint32_t(ThreadOp::I32Wait):
CHECK(emitWait(ValType::I32, 4)); case uint32_t(ThreadOp::I64Wait):
CHECK(emitWait(ValType::I64, 8)); case uint32_t(ThreadOp::Fence):
CHECK(emitFence());
case uint32_t(ThreadOp::I32AtomicLoad):
CHECK(emitAtomicLoad(ValType::I32, Scalar::Int32)); case uint32_t(ThreadOp::I64AtomicLoad):
CHECK(emitAtomicLoad(ValType::I64, Scalar::Int64)); case uint32_t(ThreadOp::I32AtomicLoad8U):
CHECK(emitAtomicLoad(ValType::I32, Scalar::Uint8)); case uint32_t(ThreadOp::I32AtomicLoad16U):
CHECK(emitAtomicLoad(ValType::I32, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicLoad8U):
CHECK(emitAtomicLoad(ValType::I64, Scalar::Uint8)); case uint32_t(ThreadOp::I64AtomicLoad16U):
CHECK(emitAtomicLoad(ValType::I64, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicLoad32U):
CHECK(emitAtomicLoad(ValType::I64, Scalar::Uint32));
case uint32_t(ThreadOp::I32AtomicStore):
CHECK(emitAtomicStore(ValType::I32, Scalar::Int32)); case uint32_t(ThreadOp::I64AtomicStore):
CHECK(emitAtomicStore(ValType::I64, Scalar::Int64)); case uint32_t(ThreadOp::I32AtomicStore8U):
CHECK(emitAtomicStore(ValType::I32, Scalar::Uint8)); case uint32_t(ThreadOp::I32AtomicStore16U):
CHECK(emitAtomicStore(ValType::I32, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicStore8U):
CHECK(emitAtomicStore(ValType::I64, Scalar::Uint8)); case uint32_t(ThreadOp::I64AtomicStore16U):
CHECK(emitAtomicStore(ValType::I64, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicStore32U):
CHECK(emitAtomicStore(ValType::I64, Scalar::Uint32));
case uint32_t(ThreadOp::I32AtomicAdd):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Add)); case uint32_t(ThreadOp::I64AtomicAdd):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Add)); case uint32_t(ThreadOp::I32AtomicAdd8U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Add)); case uint32_t(ThreadOp::I32AtomicAdd16U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Add)); case uint32_t(ThreadOp::I64AtomicAdd8U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Add)); case uint32_t(ThreadOp::I64AtomicAdd16U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Add)); case uint32_t(ThreadOp::I64AtomicAdd32U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Add));
case uint32_t(ThreadOp::I32AtomicSub):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Sub)); case uint32_t(ThreadOp::I64AtomicSub):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Sub)); case uint32_t(ThreadOp::I32AtomicSub8U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Sub)); case uint32_t(ThreadOp::I32AtomicSub16U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Sub)); case uint32_t(ThreadOp::I64AtomicSub8U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Sub)); case uint32_t(ThreadOp::I64AtomicSub16U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Sub)); case uint32_t(ThreadOp::I64AtomicSub32U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Sub));
case uint32_t(ThreadOp::I32AtomicAnd):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::And)); case uint32_t(ThreadOp::I64AtomicAnd):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::And)); case uint32_t(ThreadOp::I32AtomicAnd8U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::And)); case uint32_t(ThreadOp::I32AtomicAnd16U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::And)); case uint32_t(ThreadOp::I64AtomicAnd8U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::And)); case uint32_t(ThreadOp::I64AtomicAnd16U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::And)); case uint32_t(ThreadOp::I64AtomicAnd32U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::And));
case uint32_t(ThreadOp::I32AtomicOr):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Or)); case uint32_t(ThreadOp::I64AtomicOr):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Or)); case uint32_t(ThreadOp::I32AtomicOr8U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Or)); case uint32_t(ThreadOp::I32AtomicOr16U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Or)); case uint32_t(ThreadOp::I64AtomicOr8U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Or)); case uint32_t(ThreadOp::I64AtomicOr16U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Or)); case uint32_t(ThreadOp::I64AtomicOr32U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Or));
case uint32_t(ThreadOp::I32AtomicXor):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Int32, AtomicOp::Xor)); case uint32_t(ThreadOp::I64AtomicXor):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Int64, AtomicOp::Xor)); case uint32_t(ThreadOp::I32AtomicXor8U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint8, AtomicOp::Xor)); case uint32_t(ThreadOp::I32AtomicXor16U):
CHECK(emitAtomicRMW(ValType::I32, Scalar::Uint16, AtomicOp::Xor)); case uint32_t(ThreadOp::I64AtomicXor8U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint8, AtomicOp::Xor)); case uint32_t(ThreadOp::I64AtomicXor16U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint16, AtomicOp::Xor)); case uint32_t(ThreadOp::I64AtomicXor32U):
CHECK(emitAtomicRMW(ValType::I64, Scalar::Uint32, AtomicOp::Xor));
case uint32_t(ThreadOp::I32AtomicXchg):
CHECK(emitAtomicXchg(ValType::I32, Scalar::Int32)); case uint32_t(ThreadOp::I64AtomicXchg):
CHECK(emitAtomicXchg(ValType::I64, Scalar::Int64)); case uint32_t(ThreadOp::I32AtomicXchg8U):
CHECK(emitAtomicXchg(ValType::I32, Scalar::Uint8)); case uint32_t(ThreadOp::I32AtomicXchg16U):
CHECK(emitAtomicXchg(ValType::I32, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicXchg8U):
CHECK(emitAtomicXchg(ValType::I64, Scalar::Uint8)); case uint32_t(ThreadOp::I64AtomicXchg16U):
CHECK(emitAtomicXchg(ValType::I64, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicXchg32U):
CHECK(emitAtomicXchg(ValType::I64, Scalar::Uint32));
case uint32_t(ThreadOp::I32AtomicCmpXchg):
CHECK(emitAtomicCmpXchg(ValType::I32, Scalar::Int32)); case uint32_t(ThreadOp::I64AtomicCmpXchg):
CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Int64)); case uint32_t(ThreadOp::I32AtomicCmpXchg8U):
CHECK(emitAtomicCmpXchg(ValType::I32, Scalar::Uint8)); case uint32_t(ThreadOp::I32AtomicCmpXchg16U):
CHECK(emitAtomicCmpXchg(ValType::I32, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicCmpXchg8U):
CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Uint8)); case uint32_t(ThreadOp::I64AtomicCmpXchg16U):
CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Uint16)); case uint32_t(ThreadOp::I64AtomicCmpXchg32U):
CHECK(emitAtomicCmpXchg(ValType::I64, Scalar::Uint32));
// asm.js-specific operators case uint16_t(Op::MozPrefix): { // Perform a single feature check based on whether the opcode is asm.js // or for builtin modules. if (op.b1 <= uint32_t(MozOp::LastAsmJSOp)) { if (!codeMeta().isAsmJS()) { return iter().unrecognizedOpcode(&op);
}
} else { if (!codeMeta().isBuiltinModule()) { return iter().unrecognizedOpcode(&op);
}
}
switch (op.b1) { case uint32_t(MozOp::TeeGlobal):
CHECK(emitTeeGlobal()); case uint32_t(MozOp::I32Min): case uint32_t(MozOp::I32Max):
CHECK(emitMinMax(ValType::I32, MIRType::Int32,
MozOp(op.b1) == MozOp::I32Max)); case uint32_t(MozOp::I32Neg):
CHECK(emitUnaryWithType<MWasmNeg>(ValType::I32, MIRType::Int32)); case uint32_t(MozOp::I32BitNot):
CHECK(emitBitNot(ValType::I32, MIRType::Int32)); case uint32_t(MozOp::I32Abs):
CHECK(emitUnaryWithType<MAbs>(ValType::I32, MIRType::Int32)); case uint32_t(MozOp::F32TeeStoreF64):
CHECK(emitTeeStoreWithCoercion(ValType::F32, Scalar::Float64)); case uint32_t(MozOp::F64TeeStoreF32):
CHECK(emitTeeStoreWithCoercion(ValType::F64, Scalar::Float32)); case uint32_t(MozOp::I32TeeStore8):
CHECK(emitTeeStore(ValType::I32, Scalar::Int8)); case uint32_t(MozOp::I32TeeStore16):
CHECK(emitTeeStore(ValType::I32, Scalar::Int16)); case uint32_t(MozOp::I64TeeStore8):
CHECK(emitTeeStore(ValType::I64, Scalar::Int8)); case uint32_t(MozOp::I64TeeStore16):
CHECK(emitTeeStore(ValType::I64, Scalar::Int16)); case uint32_t(MozOp::I64TeeStore32):
CHECK(emitTeeStore(ValType::I64, Scalar::Int32)); case uint32_t(MozOp::I32TeeStore):
CHECK(emitTeeStore(ValType::I32, Scalar::Int32)); case uint32_t(MozOp::I64TeeStore):
CHECK(emitTeeStore(ValType::I64, Scalar::Int64)); case uint32_t(MozOp::F32TeeStore):
CHECK(emitTeeStore(ValType::F32, Scalar::Float32)); case uint32_t(MozOp::F64TeeStore):
CHECK(emitTeeStore(ValType::F64, Scalar::Float64)); case uint32_t(MozOp::F64Mod):
CHECK(emitRem(ValType::F64, MIRType::Double, /* isUnsigned = */ false)); case uint32_t(MozOp::F64SinNative):
CHECK(emitUnaryMathBuiltinCall(SASigSinNativeD)); case uint32_t(MozOp::F64SinFdlibm):
CHECK(emitUnaryMathBuiltinCall(SASigSinFdlibmD)); case uint32_t(MozOp::F64CosNative):
CHECK(emitUnaryMathBuiltinCall(SASigCosNativeD)); case uint32_t(MozOp::F64CosFdlibm):
CHECK(emitUnaryMathBuiltinCall(SASigCosFdlibmD)); case uint32_t(MozOp::F64TanNative):
CHECK(emitUnaryMathBuiltinCall(SASigTanNativeD)); case uint32_t(MozOp::F64TanFdlibm):
CHECK(emitUnaryMathBuiltinCall(SASigTanFdlibmD)); case uint32_t(MozOp::F64Asin):
CHECK(emitUnaryMathBuiltinCall(SASigASinD)); case uint32_t(MozOp::F64Acos):
CHECK(emitUnaryMathBuiltinCall(SASigACosD)); case uint32_t(MozOp::F64Atan):
CHECK(emitUnaryMathBuiltinCall(SASigATanD)); case uint32_t(MozOp::F64Exp):
CHECK(emitUnaryMathBuiltinCall(SASigExpD)); case uint32_t(MozOp::F64Log):
CHECK(emitUnaryMathBuiltinCall(SASigLogD)); case uint32_t(MozOp::F64Pow):
CHECK(emitBinaryMathBuiltinCall(SASigPowD)); case uint32_t(MozOp::F64Atan2):
CHECK(emitBinaryMathBuiltinCall(SASigATan2D)); case uint32_t(MozOp::OldCallDirect):
CHECK(emitCall(/* asmJSFuncDef = */ true)); case uint32_t(MozOp::OldCallIndirect):
CHECK(emitCallIndirect(/* oldStyle = */ true)); case uint32_t(MozOp::CallBuiltinModuleFunc):
CHECK(emitCallBuiltinModuleFunc()); #ifdef ENABLE_WASM_JSPI case uint32_t(MozOp::GuardSuspending):
CHECK(emitGuardSuspending()); #endif// ENABLE_WASM_JSPI
bool RootCompiler::generate() { // Only activate branch hinting if the option is enabled and some hints were // parsed. if (codeMeta_.branchHintingEnabled() && !codeMeta_.branchHints.isEmpty()) {
compileInfo_.setBranchHinting(true);
}
// Figure out what the inlining budget for this function is. If we've // already exceeded the module-level limit, the budget is zero. See // "[SMDOC] Per-function and per-module inlining limits" (WasmHeuristics.h) if (codeTailMeta_) { auto guard = codeTailMeta_->inliningBudget.lock();
if (codeTailMeta_) { auto guard = codeTailMeta_->inliningBudget.lock(); // Update the module's inlining budget accordingly. If it is already // negative, no more inlining for the module can happen, so there's no // point in updating it further. if (guard.get() >= 0) {
guard.get() -= int64_t(inliningStats_.inlinedDirectBytecodeSize);
guard.get() -= int64_t(inliningStats_.inlinedCallRefBytecodeSize); if (guard.get() < 0) {
JS_LOG(wasmPerf, Info, "CM=..%06lx RC::generate " "Inlining budget for entire module exceeded", 0xFFFFFF & (unsignedlong)uintptr_t(&codeMeta_));
}
} // If this particular root function overran the function-level // limit, note that in the module too. if (localInliningBudget_ < 0) {
funcStats_.numInliningBudgetOverruns += 1;
}
}
// Update the inlining budget accordingly. If it is already negative, no // more inlining within this root function can happen, so there's no // point in updating it further. if (localInliningBudget_ >= 0) {
localInliningBudget_ -= int64_t(inlineeBytecodeSize); #ifdef JS_JITSPEW if (localInliningBudget_ <= 0) {
JS_LOG(wasmPerf, Info, "CM=..%06lx RC::startInlineCall " "Inlining budget for fI=%u exceeded", 0xFFFFFF & (unsignedlong)uintptr_t(&codeMeta_), callerFuncIndex);
} #endif
}
// Add the callers offset to the stack of inlined caller offsets if (!inlinedCallerOffsets_.append(callerOffset)) { return nullptr;
}
// Cache a copy of the current stack of inlined caller offsets that can be // shared across all call sites
InlinedCallerOffsets inlinedCallerOffsets; if (!inlinedCallerOffsets.appendAll(inlinedCallerOffsets_)) { return nullptr;
}
if (!inliningContext_.append(std::move(inlinedCallerOffsets),
&inlinedCallerOffsetsIndex_)) { return nullptr;
}
// We should not interact with the GC heap, nor allocate from it when we are // compiling wasm code. Ion data structures have some fields for GC objects // that we do not use, yet can confuse the static analysis here. Disable it // for this function.
JS::AutoSuppressGCAnalysis nogc;
// Swap in already-allocated empty vectors to avoid malloc/free.
MOZ_ASSERT(code->empty()); if (!code->swap(masm)) { returnfalse;
}
// Create a description of the stack layout created by GenerateTrapExit().
RegisterOffsets trapExitLayout;
size_t trapExitLayoutNumWords;
GenerateTrapExitRegisterOffsets(&trapExitLayout, &trapExitLayoutNumWords);
for (const FuncCompileInput& func : inputs) {
JitSpew(JitSpew_Codegen, "\n");
JitSpew(JitSpew_Codegen, "# ================================" "==================================");
JitSpew(JitSpew_Codegen, "# ==");
JitSpew(JitSpew_Codegen, "# wasm::IonCompileFunctions: starting on function index %d",
(int)func.index);
// Build the local types vector.
ValTypeVector locals; if (!DecodeLocalEntriesWithParams(d, codeMeta, func.index, &locals)) { returnfalse;
}
// Set up for Ion compilation.
RootCompiler rootCompiler(compilerEnv, codeMeta, codeTailMeta, alloc,
locals, func, d, masm.tryNotes(),
masm.inliningContext()); if (!rootCompiler.generate()) { returnfalse;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.1.319Bemerkung:
(vorverarbeitet am 2026-09-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.