/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * Copyright 2015 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wasm/WasmIonCompile.h" #include "mozilla/MathAlgorithms.h" #include "jit/CodeGenerator.h" #include "wasm/WasmBaselineCompile.h" #include "wasm/WasmBinaryFormat.h" #include "wasm/WasmBinaryIterator.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmSignalHandlers.h" using namespace js; using namespace js::jit; using namespace js::wasm; using mozilla::DebugOnly; using mozilla::IsPowerOfTwo; using mozilla::Maybe; using mozilla::Nothing; using mozilla::Some; namespace { typedef Vector BlockVector; struct IonCompilePolicy : OpIterPolicy { // Producing output is what we're all about here. static const bool Output = true; // We store SSA definitions in the value stack. typedef MDefinition* Value; // We store loop headers and then/else blocks in the control flow stack. typedef MBasicBlock* ControlItem; }; typedef OpIter IonOpIter; class FunctionCompiler; // TlsUsage describes how the TLS register is used during a function call. enum class TlsUsage { Unused, // No particular action is taken with respect to the TLS register. Need, // The TLS register must be reloaded just before the call. CallerSaved // Same, plus space must be allocated to save/restore the TLS // register. }; static bool NeedsTls(TlsUsage usage) { return usage == TlsUsage::Need || usage == TlsUsage::CallerSaved; } // CallCompileState describes a call that is being compiled. Due to expression // nesting, multiple calls can be in the middle of compilation at the same time // and these are tracked in a stack by FunctionCompiler. class CallCompileState { // The line or bytecode of the call. uint32_t lineOrBytecode_; // A generator object that is passed each argument as it is compiled. ABIArgGenerator abi_; // The maximum number of bytes used by "child" calls, i.e., calls that occur // while evaluating the arguments of the call represented by this // CallCompileState. uint32_t maxChildStackBytes_; // Set by FunctionCompiler::finishCall(), tells the MWasmCall by how // much to bump the stack pointer before making the call. See // FunctionCompiler::startCall() comment below. uint32_t spIncrement_; // Set by FunctionCompiler::finishCall(), tells a potentially-inter-module // call the offset of the reserved space in which it can save the caller's // WasmTlsReg. uint32_t tlsStackOffset_; // Accumulates the register arguments while compiling arguments. MWasmCall::Args regArgs_; // Reserved argument for passing Instance* to builtin instance method calls. ABIArg instanceArg_; // Accumulates the stack arguments while compiling arguments. This is only // necessary to track when childClobbers_ is true so that the stack offsets // can be updated. Vector stackArgs_; // Set by child calls (i.e., calls that execute while evaluating a parent's // operands) to indicate that the child and parent call cannot reuse the // same stack space -- the parent must store its stack arguments below the // child's and increment sp when performing its call. bool childClobbers_; // Only FunctionCompiler should be directly manipulating CallCompileState. friend class FunctionCompiler; public: CallCompileState(FunctionCompiler& f, uint32_t lineOrBytecode) : lineOrBytecode_(lineOrBytecode), maxChildStackBytes_(0), spIncrement_(0), tlsStackOffset_(MWasmCall::DontSaveTls), childClobbers_(false) { } }; // Encapsulates the compilation of a single function in an asm.js module. The // function compiler handles the creation and final backend compilation of the // MIR graph. class FunctionCompiler { struct ControlFlowPatch { MControlInstruction* ins; uint32_t index; ControlFlowPatch(MControlInstruction* ins, uint32_t index) : ins(ins), index(index) {} }; typedef Vector ControlFlowPatchVector; typedef Vector ControlFlowPatchsVector; typedef Vector CallCompileStateVector; const ModuleGeneratorData& mg_; IonOpIter iter_; const FuncBytes& func_; const ValTypeVector& locals_; size_t lastReadCallSite_; TempAllocator& alloc_; MIRGraph& graph_; const CompileInfo& info_; MIRGenerator& mirGen_; MInstruction* dummyIns_; MBasicBlock* curBlock_; CallCompileStateVector callStack_; uint32_t maxStackArgBytes_; uint32_t loopDepth_; uint32_t blockDepth_; ControlFlowPatchsVector blockPatches_; FuncCompileResults& compileResults_; // TLS pointer argument to the current function. MWasmParameter* tlsPointer_; public: FunctionCompiler(const ModuleGeneratorData& mg, Decoder& decoder, const FuncBytes& func, const ValTypeVector& locals, MIRGenerator& mirGen, FuncCompileResults& compileResults) : mg_(mg), iter_(decoder, func.lineOrBytecode()), func_(func), locals_(locals), lastReadCallSite_(0), alloc_(mirGen.alloc()), graph_(mirGen.graph()), info_(mirGen.info()), mirGen_(mirGen), dummyIns_(nullptr), curBlock_(nullptr), maxStackArgBytes_(0), loopDepth_(0), blockDepth_(0), compileResults_(compileResults), tlsPointer_(nullptr) {} const ModuleGeneratorData& mg() const { return mg_; } IonOpIter& iter() { return iter_; } TempAllocator& alloc() const { return alloc_; } MacroAssembler& masm() const { return compileResults_.masm(); } const Sig& sig() const { return func_.sig(); } TrapOffset trapOffset() const { return iter_.trapOffset(); } Maybe trapIfNotAsmJS() const { return mg_.isAsmJS() ? Nothing() : Some(iter_.trapOffset()); } bool init() { // Prepare the entry block for MIR generation: const ValTypeVector& args = func_.sig().args(); if (!mirGen_.ensureBallast()) return false; if (!newBlock(/* prev */ nullptr, &curBlock_)) return false; for (ABIArgValTypeIter i(args); !i.done(); i++) { MWasmParameter* ins = MWasmParameter::New(alloc(), *i, i.mirType()); curBlock_->add(ins); curBlock_->initSlot(info().localSlot(i.index()), ins); if (!mirGen_.ensureBallast()) return false; } // Set up a parameter that receives the hidden TLS pointer argument. tlsPointer_ = MWasmParameter::New(alloc(), ABIArg(WasmTlsReg), MIRType::Pointer); curBlock_->add(tlsPointer_); if (!mirGen_.ensureBallast()) return false; for (size_t i = args.length(); i < locals_.length(); i++) { MInstruction* ins = nullptr; switch (locals_[i]) { case ValType::I32: ins = MConstant::New(alloc(), Int32Value(0), MIRType::Int32); break; case ValType::I64: ins = MConstant::NewInt64(alloc(), 0); break; case ValType::F32: ins = MConstant::New(alloc(), Float32Value(0.f), MIRType::Float32); break; case ValType::F64: ins = MConstant::New(alloc(), DoubleValue(0.0), MIRType::Double); break; case ValType::I8x16: ins = MSimdConstant::New(alloc(), SimdConstant::SplatX16(0), MIRType::Int8x16); break; case ValType::I16x8: ins = MSimdConstant::New(alloc(), SimdConstant::SplatX8(0), MIRType::Int16x8); break; case ValType::I32x4: ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0), MIRType::Int32x4); break; case ValType::F32x4: ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0.f), MIRType::Float32x4); break; case ValType::B8x16: // Bool8x16 uses the same data layout as Int8x16. ins = MSimdConstant::New(alloc(), SimdConstant::SplatX16(0), MIRType::Bool8x16); break; case ValType::B16x8: // Bool16x8 uses the same data layout as Int16x8. ins = MSimdConstant::New(alloc(), SimdConstant::SplatX8(0), MIRType::Bool16x8); break; case ValType::B32x4: // Bool32x4 uses the same data layout as Int32x4. ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0), MIRType::Bool32x4); break; } curBlock_->add(ins); curBlock_->initSlot(info().localSlot(i), ins); if (!mirGen_.ensureBallast()) return false; } dummyIns_ = MConstant::New(alloc(), Int32Value(0), MIRType::Int32); curBlock_->add(dummyIns_); addInterruptCheck(); return true; } void finish() { mirGen().initWasmMaxStackArgBytes(maxStackArgBytes_); MOZ_ASSERT(callStack_.empty()); MOZ_ASSERT(loopDepth_ == 0); MOZ_ASSERT(blockDepth_ == 0); #ifdef DEBUG for (ControlFlowPatchVector& patches : blockPatches_) MOZ_ASSERT(patches.empty()); #endif MOZ_ASSERT(inDeadCode()); MOZ_ASSERT(done(), "all bytes must be consumed"); MOZ_ASSERT(func_.callSiteLineNums().length() == lastReadCallSite_); } /************************* Read-only interface (after local scope setup) */ MIRGenerator& mirGen() const { return mirGen_; } MIRGraph& mirGraph() const { return graph_; } const CompileInfo& info() const { return info_; } MDefinition* getLocalDef(unsigned slot) { if (inDeadCode()) return nullptr; return curBlock_->getSlot(info().localSlot(slot)); } const ValTypeVector& locals() const { return locals_; } /***************************** Code generation (after local scope setup) */ MDefinition* constant(const SimdConstant& v, MIRType type) { if (inDeadCode()) return nullptr; MInstruction* constant; constant = MSimdConstant::New(alloc(), v, type); curBlock_->add(constant); return constant; } MDefinition* constant(const Value& v, MIRType type) { if (inDeadCode()) return nullptr; MConstant* constant = MConstant::New(alloc(), v, type); curBlock_->add(constant); return constant; } MDefinition* constant(int64_t i) { if (inDeadCode()) return nullptr; MConstant* constant = MConstant::NewInt64(alloc(), i); curBlock_->add(constant); return constant; } MDefinition* constant(RawF32 f) { if (inDeadCode()) return nullptr; MConstant* constant = MConstant::New(alloc(), f); curBlock_->add(constant); return constant; } MDefinition* constant(RawF64 d) { if (inDeadCode()) return nullptr; MConstant* constant = MConstant::New(alloc(), d); curBlock_->add(constant); return constant; } template MDefinition* unary(MDefinition* op) { if (inDeadCode()) return nullptr; T* ins = T::New(alloc(), op); curBlock_->add(ins); return ins; } template MDefinition* unary(MDefinition* op, MIRType type) { if (inDeadCode()) return nullptr; T* ins = T::New(alloc(), op, type); curBlock_->add(ins); return ins; } template MDefinition* binary(MDefinition* lhs, MDefinition* rhs) { if (inDeadCode()) return nullptr; T* ins = T::New(alloc(), lhs, rhs); curBlock_->add(ins); return ins; } template MDefinition* binary(MDefinition* lhs, MDefinition* rhs, MIRType type) { if (inDeadCode()) return nullptr; T* ins = T::New(alloc(), lhs, rhs, type); curBlock_->add(ins); return ins; } bool mustPreserveNaN(MIRType type) { return IsFloatingPointType(type) && mg().kind == ModuleKind::Wasm; } MDefinition* sub(MDefinition* lhs, MDefinition* rhs, MIRType type) { if (inDeadCode()) return nullptr; // wasm can't fold x - 0.0 because of NaN with custom payloads. MSub* ins = MSub::New(alloc(), lhs, rhs, type, mustPreserveNaN(type)); curBlock_->add(ins); return ins; } MDefinition* unarySimd(MDefinition* input, MSimdUnaryArith::Operation op, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(input->type()) && input->type() == type); MInstruction* ins = MSimdUnaryArith::New(alloc(), input, op); curBlock_->add(ins); return ins; } MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryArith::Operation op, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); MOZ_ASSERT(lhs->type() == type); return MSimdBinaryArith::AddLegalized(alloc(), curBlock_, lhs, rhs, op); } MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, MSimdBinaryBitwise::Operation op, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); MOZ_ASSERT(lhs->type() == type); auto* ins = MSimdBinaryBitwise::New(alloc(), lhs, rhs, op); curBlock_->add(ins); return ins; } MDefinition* binarySimdComp(MDefinition* lhs, MDefinition* rhs, MSimdBinaryComp::Operation op, SimdSign sign) { if (inDeadCode()) return nullptr; return MSimdBinaryComp::AddLegalized(alloc(), curBlock_, lhs, rhs, op, sign); } MDefinition* binarySimdSaturating(MDefinition* lhs, MDefinition* rhs, MSimdBinarySaturating::Operation op, SimdSign sign) { if (inDeadCode()) return nullptr; auto* ins = MSimdBinarySaturating::New(alloc(), lhs, rhs, op, sign); curBlock_->add(ins); return ins; } MDefinition* binarySimdShift(MDefinition* lhs, MDefinition* rhs, MSimdShift::Operation op) { if (inDeadCode()) return nullptr; return MSimdShift::AddLegalized(alloc(), curBlock_, lhs, rhs, op); } MDefinition* swizzleSimd(MDefinition* vector, const uint8_t lanes[], MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(vector->type() == type); MSimdSwizzle* ins = MSimdSwizzle::New(alloc(), vector, lanes); curBlock_->add(ins); return ins; } MDefinition* shuffleSimd(MDefinition* lhs, MDefinition* rhs, const uint8_t lanes[], MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(lhs->type() == type); MInstruction* ins = MSimdShuffle::New(alloc(), lhs, rhs, lanes); curBlock_->add(ins); return ins; } MDefinition* insertElementSimd(MDefinition* vec, MDefinition* val, unsigned lane, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(vec->type()) && vec->type() == type); MOZ_ASSERT(SimdTypeToLaneArgumentType(vec->type()) == val->type()); MSimdInsertElement* ins = MSimdInsertElement::New(alloc(), vec, val, lane); curBlock_->add(ins); return ins; } MDefinition* selectSimd(MDefinition* mask, MDefinition* lhs, MDefinition* rhs, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(mask->type())); MOZ_ASSERT(IsSimdType(lhs->type()) && rhs->type() == lhs->type()); MOZ_ASSERT(lhs->type() == type); MSimdSelect* ins = MSimdSelect::New(alloc(), mask, lhs, rhs); curBlock_->add(ins); return ins; } MDefinition* simdAllTrue(MDefinition* boolVector) { if (inDeadCode()) return nullptr; MSimdAllTrue* ins = MSimdAllTrue::New(alloc(), boolVector, MIRType::Int32); curBlock_->add(ins); return ins; } MDefinition* simdAnyTrue(MDefinition* boolVector) { if (inDeadCode()) return nullptr; MSimdAnyTrue* ins = MSimdAnyTrue::New(alloc(), boolVector, MIRType::Int32); curBlock_->add(ins); return ins; } // fromXXXBits() MDefinition* bitcastSimd(MDefinition* vec, MIRType from, MIRType to) { if (inDeadCode()) return nullptr; MOZ_ASSERT(vec->type() == from); MOZ_ASSERT(IsSimdType(from) && IsSimdType(to) && from != to); auto* ins = MSimdReinterpretCast::New(alloc(), vec, to); curBlock_->add(ins); return ins; } // Int <--> Float conversions. MDefinition* convertSimd(MDefinition* vec, MIRType from, MIRType to, SimdSign sign) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(from) && IsSimdType(to) && from != to); return MSimdConvert::AddLegalized(alloc(), curBlock_, vec, to, sign, trapOffset()); } MDefinition* splatSimd(MDefinition* v, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(type)); MOZ_ASSERT(SimdTypeToLaneArgumentType(type) == v->type()); MSimdSplat* ins = MSimdSplat::New(alloc(), v, type); curBlock_->add(ins); return ins; } MDefinition* minMax(MDefinition* lhs, MDefinition* rhs, MIRType type, bool isMax) { if (inDeadCode()) return nullptr; if (mustPreserveNaN(type)) { // Convert signaling NaN to quiet NaNs. MDefinition* zero = constant(DoubleValue(0.0), type); lhs = sub(lhs, zero, type); rhs = sub(rhs, zero, type); } MMinMax* ins = MMinMax::NewWasm(alloc(), lhs, rhs, type, isMax); curBlock_->add(ins); return ins; } MDefinition* mul(MDefinition* lhs, MDefinition* rhs, MIRType type, MMul::Mode mode) { if (inDeadCode()) return nullptr; // wasm can't fold x * 1.0 because of NaN with custom payloads. auto* ins = MMul::NewWasm(alloc(), lhs, rhs, type, mode, mustPreserveNaN(type)); curBlock_->add(ins); return ins; } MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd) { if (inDeadCode()) return nullptr; bool trapOnError = !mg().isAsmJS(); auto* ins = MDiv::New(alloc(), lhs, rhs, type, unsignd, trapOnError, trapOffset(), mustPreserveNaN(type)); curBlock_->add(ins); return ins; } MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd) { if (inDeadCode()) return nullptr; bool trapOnError = !mg().isAsmJS(); auto* ins = MMod::New(alloc(), lhs, rhs, type, unsignd, trapOnError, trapOffset()); curBlock_->add(ins); return ins; } MDefinition* bitnot(MDefinition* op) { if (inDeadCode()) return nullptr; auto* ins = MBitNot::NewInt32(alloc(), op); curBlock_->add(ins); return ins; } MDefinition* select(MDefinition* trueExpr, MDefinition* falseExpr, MDefinition* condExpr) { if (inDeadCode()) return nullptr; auto* ins = MWasmSelect::New(alloc(), trueExpr, falseExpr, condExpr); curBlock_->add(ins); return ins; } MDefinition* extendI32(MDefinition* op, bool isUnsigned) { if (inDeadCode()) return nullptr; auto* ins = MExtendInt32ToInt64::New(alloc(), op, isUnsigned); curBlock_->add(ins); return ins; } MDefinition* convertI64ToFloatingPoint(MDefinition* op, MIRType type, bool isUnsigned) { if (inDeadCode()) return nullptr; auto* ins = MInt64ToFloatingPoint::New(alloc(), op, type, isUnsigned); curBlock_->add(ins); return ins; } MDefinition* rotate(MDefinition* input, MDefinition* count, MIRType type, bool left) { if (inDeadCode()) return nullptr; auto* ins = MRotate::New(alloc(), input, count, type, left); curBlock_->add(ins); return ins; } template MDefinition* truncate(MDefinition* op, bool isUnsigned) { if (inDeadCode()) return nullptr; auto* ins = T::New(alloc(), op, isUnsigned, trapOffset()); curBlock_->add(ins); return ins; } MDefinition* compare(MDefinition* lhs, MDefinition* rhs, JSOp op, MCompare::CompareType type) { if (inDeadCode()) return nullptr; auto* ins = MCompare::New(alloc(), lhs, rhs, op, type); curBlock_->add(ins); return ins; } void assign(unsigned slot, MDefinition* def) { if (inDeadCode()) return; curBlock_->setSlot(info().localSlot(slot), def); } private: void checkOffsetAndBounds(MemoryAccessDesc* access, MDefinition** base) { // If the offset is bigger than the guard region, a separate instruction // is necessary to add the offset to the base and check for overflow. if (access->offset() >= OffsetGuardLimit || !JitOptions.wasmFoldOffsets) { auto* ins = MWasmAddOffset::New(alloc(), *base, access->offset(), trapOffset()); curBlock_->add(ins); *base = ins; access->clearOffset(); } #ifndef WASM_HUGE_MEMORY curBlock_->add(MWasmBoundsCheck::New(alloc(), *base, trapOffset())); #endif } public: MDefinition* load(MDefinition* base, MemoryAccessDesc access, ValType result) { if (inDeadCode()) return nullptr; MInstruction* load = nullptr; if (access.isPlainAsmJS()) { MOZ_ASSERT(access.offset() == 0); load = MAsmJSLoadHeap::New(alloc(), base, access.type()); } else { checkOffsetAndBounds(&access, &base); load = MWasmLoad::New(alloc(), base, access, ToMIRType(result)); } curBlock_->add(load); return load; } void store(MDefinition* base, MemoryAccessDesc access, MDefinition* v) { if (inDeadCode()) return; MInstruction* store = nullptr; if (access.isPlainAsmJS()) { MOZ_ASSERT(access.offset() == 0); store = MAsmJSStoreHeap::New(alloc(), base, access.type(), v); } else { checkOffsetAndBounds(&access, &base); store = MWasmStore::New(alloc(), base, access, v); } curBlock_->add(store); } MDefinition* atomicCompareExchangeHeap(MDefinition* base, MemoryAccessDesc access, MDefinition* oldv, MDefinition* newv) { if (inDeadCode()) return nullptr; checkOffsetAndBounds(&access, &base); auto* cas = MAsmJSCompareExchangeHeap::New(alloc(), base, access, oldv, newv, tlsPointer_); curBlock_->add(cas); return cas; } MDefinition* atomicExchangeHeap(MDefinition* base, MemoryAccessDesc access, MDefinition* value) { if (inDeadCode()) return nullptr; checkOffsetAndBounds(&access, &base); auto* cas = MAsmJSAtomicExchangeHeap::New(alloc(), base, access, value, tlsPointer_); curBlock_->add(cas); return cas; } MDefinition* atomicBinopHeap(js::jit::AtomicOp op, MDefinition* base, MemoryAccessDesc access, MDefinition* v) { if (inDeadCode()) return nullptr; checkOffsetAndBounds(&access, &base); auto* binop = MAsmJSAtomicBinopHeap::New(alloc(), op, base, access, v, tlsPointer_); curBlock_->add(binop); return binop; } MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, MIRType type) { if (inDeadCode()) return nullptr; auto* load = MWasmLoadGlobalVar::New(alloc(), type, globalDataOffset, isConst); curBlock_->add(load); return load; } void storeGlobalVar(uint32_t globalDataOffset, MDefinition* v) { if (inDeadCode()) return; curBlock_->add(MWasmStoreGlobalVar::New(alloc(), globalDataOffset, v)); } void addInterruptCheck() { // We rely on signal handlers for interrupts on Asm.JS/Wasm MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers()); } MDefinition* extractSimdElement(unsigned lane, MDefinition* base, MIRType type, SimdSign sign) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(base->type())); MOZ_ASSERT(!IsSimdType(type)); auto* ins = MSimdExtractElement::New(alloc(), base, type, lane, sign); curBlock_->add(ins); return ins; } template MDefinition* constructSimd(MDefinition* x, MDefinition* y, MDefinition* z, MDefinition* w, MIRType type) { if (inDeadCode()) return nullptr; MOZ_ASSERT(IsSimdType(type)); T* ins = T::New(alloc(), type, x, y, z, w); curBlock_->add(ins); return ins; } /***************************************************************** Calls */ // The IonMonkey backend maintains a single stack offset (from the stack // pointer to the base of the frame) by adding the total amount of spill // space required plus the maximum stack required for argument passing. // Since we do not use IonMonkey's MPrepareCall/MPassArg/MCall, we must // manually accumulate, for the entire function, the maximum required stack // space for argument passing. (This is passed to the CodeGenerator via // MIRGenerator::maxWasmStackArgBytes.) Naively, this would just be the // maximum of the stack space required for each individual call (as // determined by the call ABI). However, as an optimization, arguments are // stored to the stack immediately after evaluation (to decrease live // ranges and reduce spilling). This introduces the complexity that, // between evaluating an argument and making the call, another argument // evaluation could perform a call that also needs to store to the stack. // When this occurs childClobbers_ = true and the parent expression's // arguments are stored above the maximum depth clobbered by a child // expression. bool startCall(CallCompileState* call) { // Always push calls to maintain the invariant that if we're inDeadCode // in finishCall, we have something to pop. return callStack_.append(call); } bool passInstance(CallCompileState* args) { if (inDeadCode()) return true; // Should only pass an instance once. MOZ_ASSERT(args->instanceArg_ == ABIArg()); args->instanceArg_ = args->abi_.next(MIRType::Pointer); return true; } bool passArg(MDefinition* argDef, ValType type, CallCompileState* call) { if (inDeadCode()) return true; ABIArg arg = call->abi_.next(ToMIRType(type)); switch (arg.kind()) { #ifdef JS_CODEGEN_REGISTER_PAIR case ABIArg::GPR_PAIR: { auto mirLow = MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ true); curBlock_->add(mirLow); auto mirHigh = MWrapInt64ToInt32::New(alloc(), argDef, /* bottomHalf = */ false); curBlock_->add(mirHigh); return call->regArgs_.append(MWasmCall::Arg(AnyRegister(arg.gpr64().low), mirLow)) && call->regArgs_.append(MWasmCall::Arg(AnyRegister(arg.gpr64().high), mirHigh)); } #endif case ABIArg::GPR: case ABIArg::FPU: return call->regArgs_.append(MWasmCall::Arg(arg.reg(), argDef)); case ABIArg::Stack: { auto* mir = MWasmStackArg::New(alloc(), arg.offsetFromArgBase(), argDef); curBlock_->add(mir); return call->stackArgs_.append(mir); } default: MOZ_CRASH("Unknown ABIArg kind."); } } void propagateMaxStackArgBytes(uint32_t stackBytes) { if (callStack_.empty()) { // Outermost call maxStackArgBytes_ = Max(maxStackArgBytes_, stackBytes); return; } // Non-outermost call CallCompileState* outer = callStack_.back(); outer->maxChildStackBytes_ = Max(outer->maxChildStackBytes_, stackBytes); if (stackBytes && !outer->stackArgs_.empty()) outer->childClobbers_ = true; } bool finishCall(CallCompileState* call, TlsUsage tls) { MOZ_ALWAYS_TRUE(callStack_.popCopy() == call); if (inDeadCode()) { propagateMaxStackArgBytes(call->maxChildStackBytes_); return true; } if (NeedsTls(tls)) { if (!call->regArgs_.append(MWasmCall::Arg(AnyRegister(WasmTlsReg), tlsPointer_))) return false; } uint32_t stackBytes = call->abi_.stackBytesConsumedSoFar(); // If this is a potentially-inter-module call, allocate an extra word of // stack space to save/restore the caller's WasmTlsReg during the call. // Record the stack offset before including spIncrement since MWasmCall // will use this offset after having bumped the stack pointer. if (tls == TlsUsage::CallerSaved) { call->tlsStackOffset_ = stackBytes; stackBytes += sizeof(void*); } if (call->childClobbers_) { call->spIncrement_ = AlignBytes(call->maxChildStackBytes_, WasmStackAlignment); for (MWasmStackArg* stackArg : call->stackArgs_) stackArg->incrementOffset(call->spIncrement_); // If instanceArg_ is not initialized then instanceArg_.kind() != ABIArg::Stack if (call->instanceArg_.kind() == ABIArg::Stack) { call->instanceArg_ = ABIArg(call->instanceArg_.offsetFromArgBase() + call->spIncrement_); } stackBytes += call->spIncrement_; } else { call->spIncrement_ = 0; stackBytes = Max(stackBytes, call->maxChildStackBytes_); } propagateMaxStackArgBytes(stackBytes); return true; } bool callDirect(const Sig& sig, uint32_t funcIndex, const CallCompileState& call, MDefinition** def) { if (inDeadCode()) { *def = nullptr; return true; } CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Func); MIRType ret = ToMIRType(sig.ret()); auto callee = CalleeDesc::function(funcIndex); auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ret, call.spIncrement_, MWasmCall::DontSaveTls); if (!ins) return false; curBlock_->add(ins); *def = ins; return true; } bool callIndirect(uint32_t sigIndex, MDefinition* index, const CallCompileState& call, MDefinition** def) { if (inDeadCode()) { *def = nullptr; return true; } const SigWithId& sig = mg_.sigs[sigIndex]; CalleeDesc callee; if (mg_.isAsmJS()) { MOZ_ASSERT(sig.id.kind() == SigIdDesc::Kind::None); const TableDesc& table = mg_.tables[mg_.asmJSSigToTableIndex[sigIndex]]; MOZ_ASSERT(IsPowerOfTwo(table.limits.initial)); MOZ_ASSERT(!table.external); MOZ_ASSERT(call.tlsStackOffset_ == MWasmCall::DontSaveTls); MConstant* mask = MConstant::New(alloc(), Int32Value(table.limits.initial - 1)); curBlock_->add(mask); MBitAnd* maskedIndex = MBitAnd::New(alloc(), index, mask, MIRType::Int32); curBlock_->add(maskedIndex); index = maskedIndex; callee = CalleeDesc::asmJSTable(table); } else { MOZ_ASSERT(sig.id.kind() != SigIdDesc::Kind::None); MOZ_ASSERT(mg_.tables.length() == 1); const TableDesc& table = mg_.tables[0]; MOZ_ASSERT(table.external == (call.tlsStackOffset_ != MWasmCall::DontSaveTls)); callee = CalleeDesc::wasmTable(table, sig.id); } CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Dynamic); auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ToMIRType(sig.ret()), call.spIncrement_, call.tlsStackOffset_, index); if (!ins) return false; curBlock_->add(ins); *def = ins; return true; } bool callImport(unsigned globalDataOffset, const CallCompileState& call, ExprType ret, MDefinition** def) { if (inDeadCode()) { *def = nullptr; return true; } MOZ_ASSERT(call.tlsStackOffset_ != MWasmCall::DontSaveTls); CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Dynamic); auto callee = CalleeDesc::import(globalDataOffset); auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ToMIRType(ret), call.spIncrement_, call.tlsStackOffset_); if (!ins) return false; curBlock_->add(ins); *def = ins; return true; } bool builtinCall(SymbolicAddress builtin, const CallCompileState& call, ValType ret, MDefinition** def) { if (inDeadCode()) { *def = nullptr; return true; } CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Symbolic); auto callee = CalleeDesc::builtin(builtin); auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ToMIRType(ret), call.spIncrement_, MWasmCall::DontSaveTls); if (!ins) return false; curBlock_->add(ins); *def = ins; return true; } bool builtinInstanceMethodCall(SymbolicAddress builtin, const CallCompileState& call, ValType ret, MDefinition** def) { if (inDeadCode()) { *def = nullptr; return true; } CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Symbolic); auto* ins = MWasmCall::NewBuiltinInstanceMethodCall(alloc(), desc, builtin, call.instanceArg_, call.regArgs_, ToMIRType(ret), call.spIncrement_, call.tlsStackOffset_); if (!ins) return false; curBlock_->add(ins); *def = ins; return true; } /*********************************************** Control flow generation */ inline bool inDeadCode() const { return curBlock_ == nullptr; } void returnExpr(MDefinition* operand) { if (inDeadCode()) return; MWasmReturn* ins = MWasmReturn::New(alloc(), operand, tlsPointer_); curBlock_->end(ins); curBlock_ = nullptr; } void returnVoid() { if (inDeadCode()) return; MWasmReturnVoid* ins = MWasmReturnVoid::New(alloc(), tlsPointer_); curBlock_->end(ins); curBlock_ = nullptr; } void unreachableTrap() { if (inDeadCode()) return; auto* ins = MWasmTrap::New(alloc(), wasm::Trap::Unreachable, trapOffset()); curBlock_->end(ins); curBlock_ = nullptr; } private: static bool hasPushed(MBasicBlock* block) { uint32_t numPushed = block->stackDepth() - block->info().firstStackSlot(); MOZ_ASSERT(numPushed == 0 || numPushed == 1); return numPushed; } static MDefinition* peekPushedDef(MBasicBlock* block) { MOZ_ASSERT(hasPushed(block)); return block->getSlot(block->stackDepth() - 1); } public: void pushDef(MDefinition* def) { if (inDeadCode()) return; MOZ_ASSERT(!hasPushed(curBlock_)); if (def && def->type() != MIRType::None) curBlock_->push(def); } MDefinition* popDefIfPushed(bool shouldReturn = true) { if (!hasPushed(curBlock_)) return nullptr; MDefinition* def = curBlock_->pop(); MOZ_ASSERT_IF(def->type() == MIRType::Value, !shouldReturn); return shouldReturn ? def : nullptr; } template bool ensurePushInvariants(const GetBlock& getBlock, size_t numBlocks) { // Preserve the invariant that, for every iterated MBasicBlock, either: // every MBasicBlock has a pushed expression with the same type (to // prevent creating used phis with type Value) OR no MBasicBlock has any // pushed expression. This is required by MBasicBlock::addPredecessor. if (numBlocks < 2) return true; MBasicBlock* block = getBlock(0); bool allPushed = hasPushed(block); if (allPushed) { MIRType type = peekPushedDef(block)->type(); for (size_t i = 1; allPushed && i < numBlocks; i++) { block = getBlock(i); allPushed = hasPushed(block) && peekPushedDef(block)->type() == type; } } if (!allPushed) { for (size_t i = 0; i < numBlocks; i++) { block = getBlock(i); if (!hasPushed(block)) block->push(dummyIns_); } } return allPushed; } private: void addJoinPredecessor(MDefinition* def, MBasicBlock** joinPred) { *joinPred = curBlock_; if (inDeadCode()) return; pushDef(def); } public: bool branchAndStartThen(MDefinition* cond, MBasicBlock** elseBlock) { if (inDeadCode()) { *elseBlock = nullptr; } else { MBasicBlock* thenBlock; if (!newBlock(curBlock_, &thenBlock)) return false; if (!newBlock(curBlock_, elseBlock)) return false; curBlock_->end(MTest::New(alloc(), cond, thenBlock, *elseBlock)); curBlock_ = thenBlock; mirGraph().moveBlockToEnd(curBlock_); } return startBlock(); } bool switchToElse(MBasicBlock* elseBlock, MBasicBlock** thenJoinPred) { MDefinition* ifDef; if (!finishBlock(&ifDef)) return false; if (!elseBlock) { *thenJoinPred = nullptr; } else { addJoinPredecessor(ifDef, thenJoinPred); curBlock_ = elseBlock; mirGraph().moveBlockToEnd(curBlock_); } return startBlock(); } bool joinIfElse(MBasicBlock* thenJoinPred, MDefinition** def) { MDefinition* elseDef; if (!finishBlock(&elseDef)) return false; if (!thenJoinPred && inDeadCode()) { *def = nullptr; } else { MBasicBlock* elseJoinPred; addJoinPredecessor(elseDef, &elseJoinPred); mozilla::Array blocks; size_t numJoinPreds = 0; if (thenJoinPred) blocks[numJoinPreds++] = thenJoinPred; if (elseJoinPred) blocks[numJoinPreds++] = elseJoinPred; auto getBlock = [&](size_t i) -> MBasicBlock* { return blocks[i]; }; bool yieldsValue = ensurePushInvariants(getBlock, numJoinPreds); if (numJoinPreds == 0) { *def = nullptr; return true; } MBasicBlock* join; if (!goToNewBlock(blocks[0], &join)) return false; for (size_t i = 1; i < numJoinPreds; ++i) { if (!goToExistingBlock(blocks[i], join)) return false; } curBlock_ = join; *def = popDefIfPushed(yieldsValue); } return true; } bool startBlock() { MOZ_ASSERT_IF(blockDepth_ < blockPatches_.length(), blockPatches_[blockDepth_].empty()); blockDepth_++; return true; } bool finishBlock(MDefinition** def) { MOZ_ASSERT(blockDepth_); uint32_t topLabel = --blockDepth_; return bindBranches(topLabel, def); } bool startLoop(MBasicBlock** loopHeader) { *loopHeader = nullptr; blockDepth_++; loopDepth_++; if (inDeadCode()) return true; // Create the loop header. MOZ_ASSERT(curBlock_->loopDepth() == loopDepth_ - 1); *loopHeader = MBasicBlock::New(mirGraph(), info(), curBlock_, MBasicBlock::PENDING_LOOP_HEADER); if (!*loopHeader) return false; (*loopHeader)->setLoopDepth(loopDepth_); mirGraph().addBlock(*loopHeader); curBlock_->end(MGoto::New(alloc(), *loopHeader)); MBasicBlock* body; if (!goToNewBlock(*loopHeader, &body)) return false; curBlock_ = body; return true; } private: void fixupRedundantPhis(MBasicBlock* b) { for (size_t i = 0, depth = b->stackDepth(); i < depth; i++) { MDefinition* def = b->getSlot(i); if (def->isUnused()) b->setSlot(i, def->toPhi()->getOperand(0)); } } bool setLoopBackedge(MBasicBlock* loopEntry, MBasicBlock* loopBody, MBasicBlock* backedge) { if (!loopEntry->setBackedgeWasm(backedge)) return false; // Flag all redundant phis as unused. for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd(); phi++) { MOZ_ASSERT(phi->numOperands() == 2); if (phi->getOperand(0) == phi->getOperand(1)) phi->setUnused(); } // Fix up phis stored in the slots Vector of pending blocks. for (ControlFlowPatchVector& patches : blockPatches_) { for (ControlFlowPatch& p : patches) { MBasicBlock* block = p.ins->block(); if (block->loopDepth() >= loopEntry->loopDepth()) fixupRedundantPhis(block); } } // The loop body, if any, might be referencing recycled phis too. if (loopBody) fixupRedundantPhis(loopBody); // Discard redundant phis and add to the free list. for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd(); ) { MPhi* entryDef = *phi++; if (!entryDef->isUnused()) continue; entryDef->justReplaceAllUsesWith(entryDef->getOperand(0)); loopEntry->discardPhi(entryDef); mirGraph().addPhiToFreeList(entryDef); } return true; } public: bool closeLoop(MBasicBlock* loopHeader, MDefinition** loopResult) { MOZ_ASSERT(blockDepth_ >= 1); MOZ_ASSERT(loopDepth_); uint32_t headerLabel = blockDepth_ - 1; if (!loopHeader) { MOZ_ASSERT(inDeadCode()); MOZ_ASSERT(headerLabel >= blockPatches_.length() || blockPatches_[headerLabel].empty()); blockDepth_--; loopDepth_--; *loopResult = nullptr; return true; } // Op::Loop doesn't have an implicit backedge so temporarily set // aside the end of the loop body to bind backedges. MBasicBlock* loopBody = curBlock_; curBlock_ = nullptr; // As explained in bug 1253544, Ion apparently has an invariant that // there is only one backedge to loop headers. To handle wasm's ability // to have multiple backedges to the same loop header, we bind all those // branches as forward jumps to a single backward jump. This is // unfortunate but the optimizer is able to fold these into single jumps // to backedges. MDefinition* _; if (!bindBranches(headerLabel, &_)) return false; MOZ_ASSERT(loopHeader->loopDepth() == loopDepth_); if (curBlock_) { // We're on the loop backedge block, created by bindBranches. if (hasPushed(curBlock_)) curBlock_->pop(); MOZ_ASSERT(curBlock_->loopDepth() == loopDepth_); curBlock_->end(MGoto::New(alloc(), loopHeader)); if (!setLoopBackedge(loopHeader, loopBody, curBlock_)) return false; } curBlock_ = loopBody; loopDepth_--; // If the loop depth still at the inner loop body, correct it. if (curBlock_ && curBlock_->loopDepth() != loopDepth_) { MBasicBlock* out; if (!goToNewBlock(curBlock_, &out)) return false; curBlock_ = out; } blockDepth_ -= 1; *loopResult = inDeadCode() ? nullptr : popDefIfPushed(); return true; } bool addControlFlowPatch(MControlInstruction* ins, uint32_t relative, uint32_t index) { MOZ_ASSERT(relative < blockDepth_); uint32_t absolute = blockDepth_ - 1 - relative; if (absolute >= blockPatches_.length() && !blockPatches_.resize(absolute + 1)) return false; return blockPatches_[absolute].append(ControlFlowPatch(ins, index)); } bool br(uint32_t relativeDepth, MDefinition* maybeValue) { if (inDeadCode()) return true; MGoto* jump = MGoto::New(alloc()); if (!addControlFlowPatch(jump, relativeDepth, MGoto::TargetIndex)) return false; pushDef(maybeValue); curBlock_->end(jump); curBlock_ = nullptr; return true; } bool brIf(uint32_t relativeDepth, MDefinition* maybeValue, MDefinition* condition) { if (inDeadCode()) return true; MBasicBlock* joinBlock = nullptr; if (!newBlock(curBlock_, &joinBlock)) return false; MTest* test = MTest::New(alloc(), condition, joinBlock); if (!addControlFlowPatch(test, relativeDepth, MTest::TrueBranchIndex)) return false; pushDef(maybeValue); curBlock_->end(test); curBlock_ = joinBlock; return true; } bool brTable(MDefinition* operand, uint32_t defaultDepth, const Uint32Vector& depths, MDefinition* maybeValue) { if (inDeadCode()) return true; size_t numCases = depths.length(); MOZ_ASSERT(numCases <= INT32_MAX); MOZ_ASSERT(numCases); MTableSwitch* table = MTableSwitch::New(alloc(), operand, 0, int32_t(numCases - 1)); size_t defaultIndex; if (!table->addDefault(nullptr, &defaultIndex)) return false; if (!addControlFlowPatch(table, defaultDepth, defaultIndex)) return false; typedef HashMap, SystemAllocPolicy> IndexToCaseMap; IndexToCaseMap indexToCase; if (!indexToCase.init() || !indexToCase.put(defaultDepth, defaultIndex)) return false; for (size_t i = 0; i < numCases; i++) { uint32_t depth = depths[i]; size_t caseIndex; IndexToCaseMap::AddPtr p = indexToCase.lookupForAdd(depth); if (!p) { if (!table->addSuccessor(nullptr, &caseIndex)) return false; if (!addControlFlowPatch(table, depth, caseIndex)) return false; if (!indexToCase.add(p, depth, caseIndex)) return false; } else { caseIndex = p->value(); } if (!table->addCase(caseIndex)) return false; } pushDef(maybeValue); curBlock_->end(table); curBlock_ = nullptr; return true; } /************************************************************ DECODING ***/ uint32_t readCallSiteLineOrBytecode() { if (!func_.callSiteLineNums().empty()) return func_.callSiteLineNums()[lastReadCallSite_++]; return iter_.trapOffset().bytecodeOffset; } bool done() const { return iter_.done(); } /*************************************************************************/ private: bool newBlock(MBasicBlock* pred, MBasicBlock** block) { *block = MBasicBlock::New(mirGraph(), info(), pred, MBasicBlock::NORMAL); if (!*block) return false; mirGraph().addBlock(*block); (*block)->setLoopDepth(loopDepth_); return true; } bool goToNewBlock(MBasicBlock* pred, MBasicBlock** block) { if (!newBlock(pred, block)) return false; pred->end(MGoto::New(alloc(), *block)); return true; } bool goToExistingBlock(MBasicBlock* prev, MBasicBlock* next) { MOZ_ASSERT(prev); MOZ_ASSERT(next); prev->end(MGoto::New(alloc(), next)); return next->addPredecessor(alloc(), prev); } bool bindBranches(uint32_t absolute, MDefinition** def) { if (absolute >= blockPatches_.length() || blockPatches_[absolute].empty()) { *def = inDeadCode() ? nullptr : popDefIfPushed(); return true; } ControlFlowPatchVector& patches = blockPatches_[absolute]; auto getBlock = [&](size_t i) -> MBasicBlock* { if (i < patches.length()) return patches[i].ins->block(); return curBlock_; }; bool yieldsValue = ensurePushInvariants(getBlock, patches.length() + !!curBlock_); MBasicBlock* join = nullptr; MControlInstruction* ins = patches[0].ins; MBasicBlock* pred = ins->block(); if (!newBlock(pred, &join)) return false; pred->mark(); ins->replaceSuccessor(patches[0].index, join); for (size_t i = 1; i < patches.length(); i++) { ins = patches[i].ins; pred = ins->block(); if (!pred->isMarked()) { if (!join->addPredecessor(alloc(), pred)) return false; pred->mark(); } ins->replaceSuccessor(patches[i].index, join); } MOZ_ASSERT_IF(curBlock_, !curBlock_->isMarked()); for (uint32_t i = 0; i < join->numPredecessors(); i++) join->getPredecessor(i)->unmark(); if (curBlock_ && !goToExistingBlock(curBlock_, join)) return false; curBlock_ = join; *def = popDefIfPushed(yieldsValue); patches.clear(); return true; } }; template <> MDefinition* FunctionCompiler::unary(MDefinition* op) { if (inDeadCode()) return nullptr; auto* ins = MToFloat32::New(alloc(), op, mustPreserveNaN(op->type())); curBlock_->add(ins); return ins; } template <> MDefinition* FunctionCompiler::unary(MDefinition* op) { if (inDeadCode()) return nullptr; auto* ins = MNot::NewInt32(alloc(), op); curBlock_->add(ins); return ins; } template <> MDefinition* FunctionCompiler::unary(MDefinition* op, MIRType type) { if (inDeadCode()) return nullptr; auto* ins = MAbs::NewWasm(alloc(), op, type); curBlock_->add(ins); return ins; } } // end anonymous namespace static bool EmitBlock(FunctionCompiler& f) { return f.iter().readBlock() && f.startBlock(); } static bool EmitLoop(FunctionCompiler& f) { if (!f.iter().readLoop()) return false; MBasicBlock* loopHeader; if (!f.startLoop(&loopHeader)) return false; f.addInterruptCheck(); f.iter().controlItem() = loopHeader; return true; } static bool EmitIf(FunctionCompiler& f) { MDefinition* condition = nullptr; if (!f.iter().readIf(&condition)) return false; MBasicBlock* elseBlock; if (!f.branchAndStartThen(condition, &elseBlock)) return false; f.iter().controlItem() = elseBlock; return true; } static bool EmitElse(FunctionCompiler& f) { MBasicBlock* block = f.iter().controlItem(); ExprType thenType; MDefinition* thenValue; if (!f.iter().readElse(&thenType, &thenValue)) return false; if (!IsVoid(thenType)) f.pushDef(thenValue); if (!f.switchToElse(block, &f.iter().controlItem())) return false; return true; } static bool EmitEnd(FunctionCompiler& f) { MBasicBlock* block = f.iter().controlItem(); LabelKind kind; ExprType type; MDefinition* value; if (!f.iter().readEnd(&kind, &type, &value)) return false; if (!IsVoid(type)) f.pushDef(value); MDefinition* def = nullptr; switch (kind) { case LabelKind::Block: if (!f.finishBlock(&def)) return false; break; case LabelKind::Loop: if (!f.closeLoop(block, &def)) return false; break; case LabelKind::Then: case LabelKind::UnreachableThen: // If we didn't see an Else, create a trivial else block so that we create // a diamond anyway, to preserve Ion invariants. if (!f.switchToElse(block, &block)) return false; if (!f.joinIfElse(block, &def)) return false; break; case LabelKind::Else: if (!f.joinIfElse(block, &def)) return false; break; } if (!IsVoid(type)) { MOZ_ASSERT_IF(!f.inDeadCode(), def); f.iter().setResult(def); } return true; } static bool EmitBr(FunctionCompiler& f) { uint32_t relativeDepth; ExprType type; MDefinition* value; if (!f.iter().readBr(&relativeDepth, &type, &value)) return false; if (IsVoid(type)) { if (!f.br(relativeDepth, nullptr)) return false; } else { if (!f.br(relativeDepth, value)) return false; } return true; } static bool EmitBrIf(FunctionCompiler& f) { uint32_t relativeDepth; ExprType type; MDefinition* value; MDefinition* condition; if (!f.iter().readBrIf(&relativeDepth, &type, &value, &condition)) return false; if (IsVoid(type)) { if (!f.brIf(relativeDepth, nullptr, condition)) return false; } else { if (!f.brIf(relativeDepth, value, condition)) return false; } return true; } static bool EmitBrTable(FunctionCompiler& f) { uint32_t tableLength; ExprType type; MDefinition* value; MDefinition* index; if (!f.iter().readBrTable(&tableLength, &type, &value, &index)) return false; Uint32Vector depths; if (!depths.reserve(tableLength)) return false; for (size_t i = 0; i < tableLength; ++i) { uint32_t depth; if (!f.iter().readBrTableEntry(&type, &value, &depth)) return false; depths.infallibleAppend(depth); } // Read the default label. uint32_t defaultDepth; if (!f.iter().readBrTableDefault(&type, &value, &defaultDepth)) return false; MDefinition* maybeValue = IsVoid(type) ? nullptr : value; // If all the targets are the same, or there are no targets, we can just // use a goto. This is not just an optimization: MaybeFoldConditionBlock // assumes that tables have more than one successor. bool allSameDepth = true; for (uint32_t depth : depths) { if (depth != defaultDepth) { allSameDepth = false; break; } } if (allSameDepth) return f.br(defaultDepth, maybeValue); return f.brTable(index, defaultDepth, depths, maybeValue); } static bool EmitReturn(FunctionCompiler& f) { MDefinition* value; if (!f.iter().readReturn(&value)) return false; if (f.inDeadCode()) return true; if (IsVoid(f.sig().ret())) { f.returnVoid(); return true; } f.returnExpr(value); return true; } static bool EmitCallArgs(FunctionCompiler& f, const Sig& sig, TlsUsage tls, CallCompileState* call) { MOZ_ASSERT(NeedsTls(tls)); if (!f.startCall(call)) return false; MDefinition* arg; const ValTypeVector& argTypes = sig.args(); uint32_t numArgs = argTypes.length(); for (size_t i = 0; i < numArgs; ++i) { ValType argType = argTypes[i]; if (!f.iter().readCallArg(argType, numArgs, i, &arg)) return false; if (!f.passArg(arg, argType, call)) return false; } if (!f.iter().readCallArgsEnd(numArgs)) return false; return f.finishCall(call, tls); } static bool EmitCall(FunctionCompiler& f) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); uint32_t funcIndex; if (!f.iter().readCall(&funcIndex)) return false; if (f.inDeadCode()) return true; const Sig& sig = *f.mg().funcSigs[funcIndex]; bool import = f.mg().funcIsImport(funcIndex); CallCompileState call(f, lineOrBytecode); if (!EmitCallArgs(f, sig, import ? TlsUsage::CallerSaved : TlsUsage::Need, &call)) return false; if (!f.iter().readCallReturn(sig.ret())) return false; MDefinition* def; if (import) { uint32_t globalDataOffset = f.mg().funcImportGlobalDataOffsets[funcIndex]; if (!f.callImport(globalDataOffset, call, sig.ret(), &def)) return false; } else { if (!f.callDirect(sig, funcIndex, call, &def)) return false; } if (IsVoid(sig.ret())) return true; f.iter().setResult(def); return true; } static bool EmitCallIndirect(FunctionCompiler& f, bool oldStyle) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); uint32_t sigIndex; MDefinition* callee; if (oldStyle) { if (!f.iter().readOldCallIndirect(&sigIndex)) return false; } else { if (!f.iter().readCallIndirect(&sigIndex, &callee)) return false; } if (f.inDeadCode()) return true; const Sig& sig = f.mg().sigs[sigIndex]; TlsUsage tls = !f.mg().isAsmJS() && f.mg().tables[0].external ? TlsUsage::CallerSaved : TlsUsage::Need; CallCompileState call(f, lineOrBytecode); if (!EmitCallArgs(f, sig, tls, &call)) return false; if (oldStyle) { if (!f.iter().readOldCallIndirectCallee(&callee)) return false; } if (!f.iter().readCallReturn(sig.ret())) return false; MDefinition* def; if (!f.callIndirect(sigIndex, callee, call, &def)) return false; if (IsVoid(sig.ret())) return true; f.iter().setResult(def); return true; } static bool EmitGetLocal(FunctionCompiler& f) { uint32_t id; if (!f.iter().readGetLocal(f.locals(), &id)) return false; f.iter().setResult(f.getLocalDef(id)); return true; } static bool EmitSetLocal(FunctionCompiler& f) { uint32_t id; MDefinition* value; if (!f.iter().readSetLocal(f.locals(), &id, &value)) return false; f.assign(id, value); return true; } static bool EmitTeeLocal(FunctionCompiler& f) { uint32_t id; MDefinition* value; if (!f.iter().readTeeLocal(f.locals(), &id, &value)) return false; f.assign(id, value); return true; } static bool EmitGetGlobal(FunctionCompiler& f) { uint32_t id; if (!f.iter().readGetGlobal(f.mg().globals, &id)) return false; const GlobalDesc& global = f.mg().globals[id]; if (!global.isConstant()) { f.iter().setResult(f.loadGlobalVar(global.offset(), !global.isMutable(), ToMIRType(global.type()))); return true; } Val value = global.constantValue(); MIRType mirType = ToMIRType(value.type()); MDefinition* result; switch (value.type()) { case ValType::I32: result = f.constant(Int32Value(value.i32()), mirType); break; case ValType::I64: result = f.constant(int64_t(value.i64())); break; case ValType::F32: result = f.constant(value.f32()); break; case ValType::F64: result = f.constant(value.f64()); break; case ValType::I8x16: result = f.constant(SimdConstant::CreateX16(value.i8x16()), mirType); break; case ValType::I16x8: result = f.constant(SimdConstant::CreateX8(value.i16x8()), mirType); break; case ValType::I32x4: result = f.constant(SimdConstant::CreateX4(value.i32x4()), mirType); break; case ValType::F32x4: result = f.constant(SimdConstant::CreateX4(value.f32x4()), mirType); break; default: MOZ_CRASH("unexpected type in EmitGetGlobal"); } f.iter().setResult(result); return true; } static bool EmitSetGlobal(FunctionCompiler& f) { uint32_t id; MDefinition* value; if (!f.iter().readSetGlobal(f.mg().globals, &id, &value)) return false; const GlobalDesc& global = f.mg().globals[id]; MOZ_ASSERT(global.isMutable()); f.storeGlobalVar(global.offset(), value); return true; } static bool EmitTeeGlobal(FunctionCompiler& f) { uint32_t id; MDefinition* value; if (!f.iter().readTeeGlobal(f.mg().globals, &id, &value)) return false; const GlobalDesc& global = f.mg().globals[id]; MOZ_ASSERT(global.isMutable()); f.storeGlobalVar(global.offset(), value); return true; } template static bool EmitUnary(FunctionCompiler& f, ValType operandType) { MDefinition* input; if (!f.iter().readUnary(operandType, &input)) return false; f.iter().setResult(f.unary(input)); return true; } template static bool EmitConversion(FunctionCompiler& f, ValType operandType, ValType resultType) { MDefinition* input; if (!f.iter().readConversion(operandType, resultType, &input)) return false; f.iter().setResult(f.unary(input)); return true; } template static bool EmitUnaryWithType(FunctionCompiler& f, ValType operandType, MIRType mirType) { MDefinition* input; if (!f.iter().readUnary(operandType, &input)) return false; f.iter().setResult(f.unary(input, mirType)); return true; } template static bool EmitConversionWithType(FunctionCompiler& f, ValType operandType, ValType resultType, MIRType mirType) { MDefinition* input; if (!f.iter().readConversion(operandType, resultType, &input)) return false; f.iter().setResult(f.unary(input, mirType)); return true; } static bool EmitTruncate(FunctionCompiler& f, ValType operandType, ValType resultType, bool isUnsigned) { MDefinition* input; if (!f.iter().readConversion(operandType, resultType, &input)) return false; if (resultType == ValType::I32) { if (f.mg().isAsmJS()) f.iter().setResult(f.unary(input)); else f.iter().setResult(f.truncate(input, isUnsigned)); } else { MOZ_ASSERT(resultType == ValType::I64); MOZ_ASSERT(!f.mg().isAsmJS()); f.iter().setResult(f.truncate(input, isUnsigned)); } return true; } static bool EmitExtendI32(FunctionCompiler& f, bool isUnsigned) { MDefinition* input; if (!f.iter().readConversion(ValType::I32, ValType::I64, &input)) return false; f.iter().setResult(f.extendI32(input, isUnsigned)); return true; } static bool EmitConvertI64ToFloatingPoint(FunctionCompiler& f, ValType resultType, MIRType mirType, bool isUnsigned) { MDefinition* input; if (!f.iter().readConversion(ValType::I64, resultType, &input)) return false; f.iter().setResult(f.convertI64ToFloatingPoint(input, mirType, isUnsigned)); return true; } static bool EmitReinterpret(FunctionCompiler& f, ValType resultType, ValType operandType, MIRType mirType) { MDefinition* input; if (!f.iter().readConversion(operandType, resultType, &input)) return false; f.iter().setResult(f.unary(input, mirType)); return true; } static bool EmitAdd(FunctionCompiler& f, ValType type, MIRType mirType) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(type, &lhs, &rhs)) return false; f.iter().setResult(f.binary(lhs, rhs, mirType)); return true; } static bool EmitSub(FunctionCompiler& f, ValType type, MIRType mirType) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(type, &lhs, &rhs)) return false; f.iter().setResult(f.sub(lhs, rhs, mirType)); return true; } static bool EmitRotate(FunctionCompiler& f, ValType type, bool isLeftRotation) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(type, &lhs, &rhs)) return false; MDefinition* result = f.rotate(lhs, rhs, ToMIRType(type), isLeftRotation); f.iter().setResult(result); return true; } static bool EmitBitNot(FunctionCompiler& f, ValType operandType) { MDefinition* input; if (!f.iter().readUnary(operandType, &input)) return false; f.iter().setResult(f.bitnot(input)); return true; } template static bool EmitBitwise(FunctionCompiler& f, ValType operandType, MIRType mirType) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.binary(lhs, rhs, mirType)); return true; } static bool EmitMul(FunctionCompiler& f, ValType operandType, MIRType mirType) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.mul(lhs, rhs, mirType, mirType == MIRType::Int32 ? MMul::Integer : MMul::Normal)); return true; } static bool EmitDiv(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isUnsigned) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.div(lhs, rhs, mirType, isUnsigned)); return true; } static bool EmitRem(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isUnsigned) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.mod(lhs, rhs, mirType, isUnsigned)); return true; } static bool EmitMinMax(FunctionCompiler& f, ValType operandType, MIRType mirType, bool isMax) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.minMax(lhs, rhs, mirType, isMax)); return true; } static bool EmitCopySign(FunctionCompiler& f, ValType operandType) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.binary(lhs, rhs, ToMIRType(operandType))); return true; } static bool EmitComparison(FunctionCompiler& f, ValType operandType, JSOp compareOp, MCompare::CompareType compareType) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readComparison(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.compare(lhs, rhs, compareOp, compareType)); return true; } static bool EmitSelect(FunctionCompiler& f) { ValType type; MDefinition* trueValue; MDefinition* falseValue; MDefinition* condition; if (!f.iter().readSelect(&type, &trueValue, &falseValue, &condition)) return false; f.iter().setResult(f.select(trueValue, falseValue, condition)); return true; } static bool EmitLoad(FunctionCompiler& f, ValType type, Scalar::Type viewType) { LinearMemoryAddress addr; if (!f.iter().readLoad(type, Scalar::byteSize(viewType), &addr)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); f.iter().setResult(f.load(addr.base, access, type)); return true; } static bool EmitStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType) { LinearMemoryAddress addr; MDefinition* value; if (!f.iter().readStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); f.store(addr.base, access, value); return true; } static bool EmitTeeStore(FunctionCompiler& f, ValType resultType, Scalar::Type viewType) { LinearMemoryAddress addr; MDefinition* value; if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); f.store(addr.base, access, value); return true; } static bool EmitTeeStoreWithCoercion(FunctionCompiler& f, ValType resultType, Scalar::Type viewType) { LinearMemoryAddress addr; MDefinition* value; if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; if (resultType == ValType::F32 && viewType == Scalar::Float64) value = f.unary(value); else if (resultType == ValType::F64 && viewType == Scalar::Float32) value = f.unary(value); else MOZ_CRASH("unexpected coerced store"); MemoryAccessDesc access(viewType, addr.align, addr.offset, f.trapIfNotAsmJS()); f.store(addr.base, access, value); return true; } static bool EmitUnaryMathBuiltinCall(FunctionCompiler& f, SymbolicAddress callee, ValType operandType) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); CallCompileState call(f, lineOrBytecode); if (!f.startCall(&call)) return false; MDefinition* input; if (!f.iter().readUnary(operandType, &input)) return false; if (!f.passArg(input, operandType, &call)) return false; if (!f.finishCall(&call, TlsUsage::Unused)) return false; MDefinition* def; if (!f.builtinCall(callee, call, operandType, &def)) return false; f.iter().setResult(def); return true; } static bool EmitBinaryMathBuiltinCall(FunctionCompiler& f, SymbolicAddress callee, ValType operandType) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); CallCompileState call(f, lineOrBytecode); if (!f.startCall(&call)) return false; MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(operandType, &lhs, &rhs)) return false; if (!f.passArg(lhs, operandType, &call)) return false; if (!f.passArg(rhs, operandType, &call)) return false; if (!f.finishCall(&call, TlsUsage::Unused)) return false; MDefinition* def; if (!f.builtinCall(callee, call, operandType, &def)) return false; f.iter().setResult(def); return true; } static bool EmitAtomicsLoad(FunctionCompiler& f) { LinearMemoryAddress addr; Scalar::Type viewType; if (!f.iter().readAtomicLoad(&addr, &viewType)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), 0, MembarBeforeLoad, MembarAfterLoad); f.iter().setResult(f.load(addr.base, access, ValType::I32)); return true; } static bool EmitAtomicsStore(FunctionCompiler& f) { LinearMemoryAddress addr; Scalar::Type viewType; MDefinition* value; if (!f.iter().readAtomicStore(&addr, &viewType, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), 0, MembarBeforeStore, MembarAfterStore); f.store(addr.base, access, value); f.iter().setResult(value); return true; } static bool EmitAtomicsBinOp(FunctionCompiler& f) { LinearMemoryAddress addr; Scalar::Type viewType; jit::AtomicOp op; MDefinition* value; if (!f.iter().readAtomicBinOp(&addr, &viewType, &op, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset())); f.iter().setResult(f.atomicBinopHeap(op, addr.base, access, value)); return true; } static bool EmitAtomicsCompareExchange(FunctionCompiler& f) { LinearMemoryAddress addr; Scalar::Type viewType; MDefinition* oldValue; MDefinition* newValue; if (!f.iter().readAtomicCompareExchange(&addr, &viewType, &oldValue, &newValue)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset())); f.iter().setResult(f.atomicCompareExchangeHeap(addr.base, access, oldValue, newValue)); return true; } static bool EmitAtomicsExchange(FunctionCompiler& f) { LinearMemoryAddress addr; Scalar::Type viewType; MDefinition* value; if (!f.iter().readAtomicExchange(&addr, &viewType, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset())); f.iter().setResult(f.atomicExchangeHeap(addr.base, access, value)); return true; } static bool EmitSimdUnary(FunctionCompiler& f, ValType type, SimdOperation simdOp) { MSimdUnaryArith::Operation op; switch (simdOp) { case SimdOperation::Fn_abs: op = MSimdUnaryArith::abs; break; case SimdOperation::Fn_neg: op = MSimdUnaryArith::neg; break; case SimdOperation::Fn_not: op = MSimdUnaryArith::not_; break; case SimdOperation::Fn_sqrt: op = MSimdUnaryArith::sqrt; break; case SimdOperation::Fn_reciprocalApproximation: op = MSimdUnaryArith::reciprocalApproximation; break; case SimdOperation::Fn_reciprocalSqrtApproximation: op = MSimdUnaryArith::reciprocalSqrtApproximation; break; default: MOZ_CRASH("not a simd unary arithmetic operation"); } MDefinition* input; if (!f.iter().readUnary(type, &input)) return false; f.iter().setResult(f.unarySimd(input, op, ToMIRType(type))); return true; } template inline bool EmitSimdBinary(FunctionCompiler& f, ValType type, OpKind op) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(type, &lhs, &rhs)) return false; f.iter().setResult(f.binarySimd(lhs, rhs, op, ToMIRType(type))); return true; } static bool EmitSimdBinaryComp(FunctionCompiler& f, ValType operandType, MSimdBinaryComp::Operation op, SimdSign sign) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readSimdComparison(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.binarySimdComp(lhs, rhs, op, sign)); return true; } static bool EmitSimdBinarySaturating(FunctionCompiler& f, ValType type, MSimdBinarySaturating::Operation op, SimdSign sign) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readBinary(type, &lhs, &rhs)) return false; f.iter().setResult(f.binarySimdSaturating(lhs, rhs, op, sign)); return true; } static bool EmitSimdShift(FunctionCompiler& f, ValType operandType, MSimdShift::Operation op) { MDefinition* lhs; MDefinition* rhs; if (!f.iter().readSimdShiftByScalar(operandType, &lhs, &rhs)) return false; f.iter().setResult(f.binarySimdShift(lhs, rhs, op)); return true; } static ValType SimdToLaneType(ValType type) { switch (type) { case ValType::I8x16: case ValType::I16x8: case ValType::I32x4: return ValType::I32; case ValType::F32x4: return ValType::F32; case ValType::B8x16: case ValType::B16x8: case ValType::B32x4: return ValType::I32; // Boolean lanes are Int32 in asm. case ValType::I32: case ValType::I64: case ValType::F32: case ValType::F64: break; } MOZ_CRASH("bad simd type"); } static bool EmitExtractLane(FunctionCompiler& f, ValType operandType, SimdSign sign) { uint8_t lane; MDefinition* vector; if (!f.iter().readExtractLane(operandType, &lane, &vector)) return false; f.iter().setResult(f.extractSimdElement(lane, vector, ToMIRType(SimdToLaneType(operandType)), sign)); return true; } // Emit an I32 expression and then convert it to a boolean SIMD lane value, i.e. -1 or 0. static MDefinition* EmitSimdBooleanLaneExpr(FunctionCompiler& f, MDefinition* i32) { // Compute !i32 - 1 to force the value range into {0, -1}. MDefinition* noti32 = f.unary(i32); return f.binary(noti32, f.constant(Int32Value(1), MIRType::Int32), MIRType::Int32); } static bool EmitSimdReplaceLane(FunctionCompiler& f, ValType simdType) { if (IsSimdBoolType(simdType)) f.iter().setResult(EmitSimdBooleanLaneExpr(f, f.iter().getResult())); uint8_t lane; MDefinition* vector; MDefinition* scalar; if (!f.iter().readReplaceLane(simdType, &lane, &vector, &scalar)) return false; f.iter().setResult(f.insertElementSimd(vector, scalar, lane, ToMIRType(simdType))); return true; } inline bool EmitSimdBitcast(FunctionCompiler& f, ValType fromType, ValType toType) { MDefinition* input; if (!f.iter().readConversion(fromType, toType, &input)) return false; f.iter().setResult(f.bitcastSimd(input, ToMIRType(fromType), ToMIRType(toType))); return true; } inline bool EmitSimdConvert(FunctionCompiler& f, ValType fromType, ValType toType, SimdSign sign) { MDefinition* input; if (!f.iter().readConversion(fromType, toType, &input)) return false; f.iter().setResult(f.convertSimd(input, ToMIRType(fromType), ToMIRType(toType), sign)); return true; } static bool EmitSimdSwizzle(FunctionCompiler& f, ValType simdType) { uint8_t lanes[16]; MDefinition* vector; if (!f.iter().readSwizzle(simdType, &lanes, &vector)) return false; f.iter().setResult(f.swizzleSimd(vector, lanes, ToMIRType(simdType))); return true; } static bool EmitSimdShuffle(FunctionCompiler& f, ValType simdType) { uint8_t lanes[16]; MDefinition* lhs; MDefinition* rhs; if (!f.iter().readShuffle(simdType, &lanes, &lhs, &rhs)) return false; f.iter().setResult(f.shuffleSimd(lhs, rhs, lanes, ToMIRType(simdType))); return true; } static inline Scalar::Type SimdExprTypeToViewType(ValType type, unsigned* defaultNumElems) { switch (type) { case ValType::I8x16: *defaultNumElems = 16; return Scalar::Int8x16; case ValType::I16x8: *defaultNumElems = 8; return Scalar::Int16x8; case ValType::I32x4: *defaultNumElems = 4; return Scalar::Int32x4; case ValType::F32x4: *defaultNumElems = 4; return Scalar::Float32x4; default: break; } MOZ_CRASH("type not handled in SimdExprTypeToViewType"); } static bool EmitSimdLoad(FunctionCompiler& f, ValType resultType, unsigned numElems) { unsigned defaultNumElems; Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems); if (!numElems) numElems = defaultNumElems; LinearMemoryAddress addr; if (!f.iter().readLoad(resultType, Scalar::byteSize(viewType), &addr)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), numElems); f.iter().setResult(f.load(addr.base, access, resultType)); return true; } static bool EmitSimdStore(FunctionCompiler& f, ValType resultType, unsigned numElems) { unsigned defaultNumElems; Scalar::Type viewType = SimdExprTypeToViewType(resultType, &defaultNumElems); if (!numElems) numElems = defaultNumElems; LinearMemoryAddress addr; MDefinition* value; if (!f.iter().readTeeStore(resultType, Scalar::byteSize(viewType), &addr, &value)) return false; MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(f.trapOffset()), numElems); f.store(addr.base, access, value); return true; } static bool EmitSimdSelect(FunctionCompiler& f, ValType simdType) { MDefinition* trueValue; MDefinition* falseValue; MDefinition* condition; if (!f.iter().readSimdSelect(simdType, &trueValue, &falseValue, &condition)) return false; f.iter().setResult(f.selectSimd(condition, trueValue, falseValue, ToMIRType(simdType))); return true; } static bool EmitSimdAllTrue(FunctionCompiler& f, ValType operandType) { MDefinition* input; if (!f.iter().readSimdBooleanReduction(operandType, &input)) return false; f.iter().setResult(f.simdAllTrue(input)); return true; } static bool EmitSimdAnyTrue(FunctionCompiler& f, ValType operandType) { MDefinition* input; if (!f.iter().readSimdBooleanReduction(operandType, &input)) return false; f.iter().setResult(f.simdAnyTrue(input)); return true; } static bool EmitSimdSplat(FunctionCompiler& f, ValType simdType) { if (IsSimdBoolType(simdType)) f.iter().setResult(EmitSimdBooleanLaneExpr(f, f.iter().getResult())); MDefinition* input; if (!f.iter().readSplat(simdType, &input)) return false; f.iter().setResult(f.splatSimd(input, ToMIRType(simdType))); return true; } // Build a SIMD vector by inserting lanes one at a time into an initial constant. static bool EmitSimdChainedCtor(FunctionCompiler& f, ValType valType, MIRType type, const SimdConstant& init) { const unsigned length = SimdTypeToLength(type); MDefinition* val = f.constant(init, type); for (unsigned i = 0; i < length; i++) { MDefinition* scalar = 0; if (!f.iter().readSimdCtorArg(ValType::I32, length, i, &scalar)) return false; val = f.insertElementSimd(val, scalar, i, type); } if (!f.iter().readSimdCtorArgsEnd(length) || !f.iter().readSimdCtorReturn(valType)) return false; f.iter().setResult(val); return true; } // Build a boolean SIMD vector by inserting lanes one at a time into an initial constant. static bool EmitSimdBooleanChainedCtor(FunctionCompiler& f, ValType valType, MIRType type, const SimdConstant& init) { const unsigned length = SimdTypeToLength(type); MDefinition* val = f.constant(init, type); for (unsigned i = 0; i < length; i++) { MDefinition* scalar = 0; if (!f.iter().readSimdCtorArg(ValType::I32, length, i, &scalar)) return false; val = f.insertElementSimd(val, EmitSimdBooleanLaneExpr(f, scalar), i, type); } if (!f.iter().readSimdCtorArgsEnd(length) || !f.iter().readSimdCtorReturn(valType)) return false; f.iter().setResult(val); return true; } static bool EmitSimdCtor(FunctionCompiler& f, ValType type) { if (!f.iter().readSimdCtor()) return false; switch (type) { case ValType::I8x16: return EmitSimdChainedCtor(f, type, MIRType::Int8x16, SimdConstant::SplatX16(0)); case ValType::I16x8: return EmitSimdChainedCtor(f, type, MIRType::Int16x8, SimdConstant::SplatX8(0)); case ValType::I32x4: { MDefinition* args[4]; for (unsigned i = 0; i < 4; i++) { if (!f.iter().readSimdCtorArg(ValType::I32, 4, i, &args[i])) return false; } if (!f.iter().readSimdCtorArgsEnd(4) || !f.iter().readSimdCtorReturn(type)) return false; f.iter().setResult(f.constructSimd(args[0], args[1], args[2], args[3], MIRType::Int32x4)); return true; } case ValType::F32x4: { MDefinition* args[4]; for (unsigned i = 0; i < 4; i++) { if (!f.iter().readSimdCtorArg(ValType::F32, 4, i, &args[i])) return false; } if (!f.iter().readSimdCtorArgsEnd(4) || !f.iter().readSimdCtorReturn(type)) return false; f.iter().setResult(f.constructSimd(args[0], args[1], args[2], args[3], MIRType::Float32x4)); return true; } case ValType::B8x16: return EmitSimdBooleanChainedCtor(f, type, MIRType::Bool8x16, SimdConstant::SplatX16(0)); case ValType::B16x8: return EmitSimdBooleanChainedCtor(f, type, MIRType::Bool16x8, SimdConstant::SplatX8(0)); case ValType::B32x4: { MDefinition* args[4]; for (unsigned i = 0; i < 4; i++) { MDefinition* i32; if (!f.iter().readSimdCtorArg(ValType::I32, 4, i, &i32)) return false; args[i] = EmitSimdBooleanLaneExpr(f, i32); } if (!f.iter().readSimdCtorArgsEnd(4) || !f.iter().readSimdCtorReturn(type)) return false; f.iter().setResult(f.constructSimd(args[0], args[1], args[2], args[3], MIRType::Bool32x4)); return true; } case ValType::I32: case ValType::I64: case ValType::F32: case ValType::F64: break; } MOZ_CRASH("unexpected SIMD type"); } static bool EmitSimdOp(FunctionCompiler& f, ValType type, SimdOperation op, SimdSign sign) { switch (op) { case SimdOperation::Constructor: return EmitSimdCtor(f, type); case SimdOperation::Fn_extractLane: return EmitExtractLane(f, type, sign); case SimdOperation::Fn_replaceLane: return EmitSimdReplaceLane(f, type); case SimdOperation::Fn_check: MOZ_CRASH("only used in asm.js' type system"); case SimdOperation::Fn_splat: return EmitSimdSplat(f, type); case SimdOperation::Fn_select: return EmitSimdSelect(f, type); case SimdOperation::Fn_swizzle: return EmitSimdSwizzle(f, type); case SimdOperation::Fn_shuffle: return EmitSimdShuffle(f, type); case SimdOperation::Fn_load: return EmitSimdLoad(f, type, 0); case SimdOperation::Fn_load1: return EmitSimdLoad(f, type, 1); case SimdOperation::Fn_load2: return EmitSimdLoad(f, type, 2); case SimdOperation::Fn_store: return EmitSimdStore(f, type, 0); case SimdOperation::Fn_store1: return EmitSimdStore(f, type, 1); case SimdOperation::Fn_store2: return EmitSimdStore(f, type, 2); case SimdOperation::Fn_allTrue: return EmitSimdAllTrue(f, type); case SimdOperation::Fn_anyTrue: return EmitSimdAnyTrue(f, type); case SimdOperation::Fn_abs: case SimdOperation::Fn_neg: case SimdOperation::Fn_not: case SimdOperation::Fn_sqrt: case SimdOperation::Fn_reciprocalApproximation: case SimdOperation::Fn_reciprocalSqrtApproximation: return EmitSimdUnary(f, type, op); case SimdOperation::Fn_shiftLeftByScalar: return EmitSimdShift(f, type, MSimdShift::lsh); case SimdOperation::Fn_shiftRightByScalar: return EmitSimdShift(f, type, MSimdShift::rshForSign(sign)); #define _CASE(OP) \ case SimdOperation::Fn_##OP: \ return EmitSimdBinaryComp(f, type, MSimdBinaryComp::OP, sign); FOREACH_COMP_SIMD_OP(_CASE) #undef _CASE case SimdOperation::Fn_and: return EmitSimdBinary(f, type, MSimdBinaryBitwise::and_); case SimdOperation::Fn_or: return EmitSimdBinary(f, type, MSimdBinaryBitwise::or_); case SimdOperation::Fn_xor: return EmitSimdBinary(f, type, MSimdBinaryBitwise::xor_); #define _CASE(OP) \ case SimdOperation::Fn_##OP: \ return EmitSimdBinary(f, type, MSimdBinaryArith::Op_##OP); FOREACH_NUMERIC_SIMD_BINOP(_CASE) FOREACH_FLOAT_SIMD_BINOP(_CASE) #undef _CASE case SimdOperation::Fn_addSaturate: return EmitSimdBinarySaturating(f, type, MSimdBinarySaturating::add, sign); case SimdOperation::Fn_subSaturate: return EmitSimdBinarySaturating(f, type, MSimdBinarySaturating::sub, sign); case SimdOperation::Fn_fromFloat32x4: return EmitSimdConvert(f, ValType::F32x4, type, sign); case SimdOperation::Fn_fromInt32x4: return EmitSimdConvert(f, ValType::I32x4, type, SimdSign::Signed); case SimdOperation::Fn_fromUint32x4: return EmitSimdConvert(f, ValType::I32x4, type, SimdSign::Unsigned); case SimdOperation::Fn_fromInt8x16Bits: case SimdOperation::Fn_fromUint8x16Bits: return EmitSimdBitcast(f, ValType::I8x16, type); case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromInt16x8Bits: return EmitSimdBitcast(f, ValType::I16x8, type); case SimdOperation::Fn_fromInt32x4Bits: case SimdOperation::Fn_fromUint32x4Bits: return EmitSimdBitcast(f, ValType::I32x4, type); case SimdOperation::Fn_fromFloat32x4Bits: return EmitSimdBitcast(f, ValType::F32x4, type); case SimdOperation::Fn_load3: case SimdOperation::Fn_store3: case SimdOperation::Fn_fromFloat64x2Bits: MOZ_CRASH("NYI"); } MOZ_CRASH("unexpected opcode"); } static bool EmitGrowMemory(FunctionCompiler& f) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); CallCompileState args(f, lineOrBytecode); if (!f.startCall(&args)) return false; if (!f.passInstance(&args)) return false; MDefinition* delta; if (!f.iter().readGrowMemory(&delta)) return false; if (!f.passArg(delta, ValType::I32, &args)) return false; // As a short-cut, pretend this is an inter-module call so that any pinned // heap pointer will be reloaded after the call. This hack will go away once // we can stop pinning registers. f.finishCall(&args, TlsUsage::CallerSaved); MDefinition* ret; if (!f.builtinInstanceMethodCall(SymbolicAddress::GrowMemory, args, ValType::I32, &ret)) return false; f.iter().setResult(ret); return true; } static bool EmitCurrentMemory(FunctionCompiler& f) { uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode(); CallCompileState args(f, lineOrBytecode); if (!f.iter().readCurrentMemory()) return false; if (!f.startCall(&args)) return false; if (!f.passInstance(&args)) return false; f.finishCall(&args, TlsUsage::Need); MDefinition* ret; if (!f.builtinInstanceMethodCall(SymbolicAddress::CurrentMemory, args, ValType::I32, &ret)) return false; f.iter().setResult(ret); return true; } static bool EmitExpr(FunctionCompiler& f) { if (!f.mirGen().ensureBallast()) return false; uint16_t u16; MOZ_ALWAYS_TRUE(f.iter().readOp(&u16)); Op op = Op(u16); switch (op) { // Control opcodes case Op::Nop: return f.iter().readNop(); case Op::Drop: return f.iter().readDrop(); case Op::Block: return EmitBlock(f); case Op::Loop: return EmitLoop(f); case Op::If: return EmitIf(f); case Op::Else: return EmitElse(f); case Op::End: return EmitEnd(f); case Op::Br: return EmitBr(f); case Op::BrIf: return EmitBrIf(f); case Op::BrTable: return EmitBrTable(f); case Op::Return: return EmitReturn(f); case Op::Unreachable: if (!f.iter().readUnreachable()) return false; f.unreachableTrap(); return true; // Calls case Op::Call: return EmitCall(f); case Op::CallIndirect: return EmitCallIndirect(f, /* oldStyle = */ false); case Op::OldCallIndirect: return EmitCallIndirect(f, /* oldStyle = */ true); // Locals and globals case Op::GetLocal: return EmitGetLocal(f); case Op::SetLocal: return EmitSetLocal(f); case Op::TeeLocal: return EmitTeeLocal(f); case Op::GetGlobal: return EmitGetGlobal(f); case Op::SetGlobal: return EmitSetGlobal(f); case Op::TeeGlobal: return EmitTeeGlobal(f); // Select case Op::Select: return EmitSelect(f); // I32 case Op::I32Const: { int32_t i32; if (!f.iter().readI32Const(&i32)) return false; f.iter().setResult(f.constant(Int32Value(i32), MIRType::Int32)); return true; } case Op::I32Add: return EmitAdd(f, ValType::I32, MIRType::Int32); case Op::I32Sub: return EmitSub(f, ValType::I32, MIRType::Int32); case Op::I32Mul: return EmitMul(f, ValType::I32, MIRType::Int32); case Op::I32DivS: case Op::I32DivU: return EmitDiv(f, ValType::I32, MIRType::Int32, op == Op::I32DivU); case Op::I32RemS: case Op::I32RemU: return EmitRem(f, ValType::I32, MIRType::Int32, op == Op::I32RemU); case Op::I32Min: case Op::I32Max: return EmitMinMax(f, ValType::I32, MIRType::Int32, op == Op::I32Max); case Op::I32Eqz: return EmitConversion(f, ValType::I32, ValType::I32); case Op::I32TruncSF32: case Op::I32TruncUF32: return EmitTruncate(f, ValType::F32, ValType::I32, op == Op::I32TruncUF32); case Op::I32TruncSF64: case Op::I32TruncUF64: return EmitTruncate(f, ValType::F64, ValType::I32, op == Op::I32TruncUF64); case Op::I32WrapI64: return EmitConversion(f, ValType::I64, ValType::I32); case Op::I32ReinterpretF32: return EmitReinterpret(f, ValType::I32, ValType::F32, MIRType::Int32); case Op::I32Clz: return EmitUnaryWithType(f, ValType::I32, MIRType::Int32); case Op::I32Ctz: return EmitUnaryWithType(f, ValType::I32, MIRType::Int32); case Op::I32Popcnt: return EmitUnaryWithType(f, ValType::I32, MIRType::Int32); case Op::I32Abs: return EmitUnaryWithType(f, ValType::I32, MIRType::Int32); case Op::I32Neg: return EmitUnaryWithType(f, ValType::I32, MIRType::Int32); case Op::I32Or: return EmitBitwise(f, ValType::I32, MIRType::Int32); case Op::I32And: return EmitBitwise(f, ValType::I32, MIRType::Int32); case Op::I32Xor: return EmitBitwise(f, ValType::I32, MIRType::Int32); case Op::I32Shl: return EmitBitwise(f, ValType::I32, MIRType::Int32); case Op::I32ShrS: return EmitBitwise(f, ValType::I32, MIRType::Int32); case Op::I32ShrU: return EmitBitwise(f, ValType::I32, MIRType::Int32); case Op::I32BitNot: return EmitBitNot(f, ValType::I32); case Op::I32Load8S: return EmitLoad(f, ValType::I32, Scalar::Int8); case Op::I32Load8U: return EmitLoad(f, ValType::I32, Scalar::Uint8); case Op::I32Load16S: return EmitLoad(f, ValType::I32, Scalar::Int16); case Op::I32Load16U: return EmitLoad(f, ValType::I32, Scalar::Uint16); case Op::I32Load: return EmitLoad(f, ValType::I32, Scalar::Int32); case Op::I32Store8: return EmitStore(f, ValType::I32, Scalar::Int8); case Op::I32TeeStore8: return EmitTeeStore(f, ValType::I32, Scalar::Int8); case Op::I32Store16: return EmitStore(f, ValType::I32, Scalar::Int16); case Op::I32TeeStore16: return EmitTeeStore(f, ValType::I32, Scalar::Int16); case Op::I32Store: return EmitStore(f, ValType::I32, Scalar::Int32); case Op::I32TeeStore: return EmitTeeStore(f, ValType::I32, Scalar::Int32); case Op::I32Rotr: case Op::I32Rotl: return EmitRotate(f, ValType::I32, op == Op::I32Rotl); // I64 case Op::I64Const: { int64_t i64; if (!f.iter().readI64Const(&i64)) return false; f.iter().setResult(f.constant(i64)); return true; } case Op::I64Add: return EmitAdd(f, ValType::I64, MIRType::Int64); case Op::I64Sub: return EmitSub(f, ValType::I64, MIRType::Int64); case Op::I64Mul: return EmitMul(f, ValType::I64, MIRType::Int64); case Op::I64DivS: case Op::I64DivU: return EmitDiv(f, ValType::I64, MIRType::Int64, op == Op::I64DivU); case Op::I64RemS: case Op::I64RemU: return EmitRem(f, ValType::I64, MIRType::Int64, op == Op::I64RemU); case Op::I64TruncSF32: case Op::I64TruncUF32: return EmitTruncate(f, ValType::F32, ValType::I64, op == Op::I64TruncUF32); case Op::I64TruncSF64: case Op::I64TruncUF64: return EmitTruncate(f, ValType::F64, ValType::I64, op == Op::I64TruncUF64); case Op::I64ExtendSI32: case Op::I64ExtendUI32: return EmitExtendI32(f, op == Op::I64ExtendUI32); case Op::I64ReinterpretF64: return EmitReinterpret(f, ValType::I64, ValType::F64, MIRType::Int64); case Op::I64Or: return EmitBitwise(f, ValType::I64, MIRType::Int64); case Op::I64And: return EmitBitwise(f, ValType::I64, MIRType::Int64); case Op::I64Xor: return EmitBitwise(f, ValType::I64, MIRType::Int64); case Op::I64Shl: return EmitBitwise(f, ValType::I64, MIRType::Int64); case Op::I64ShrS: return EmitBitwise(f, ValType::I64, MIRType::Int64); case Op::I64ShrU: return EmitBitwise(f, ValType::I64, MIRType::Int64); case Op::I64Rotr: case Op::I64Rotl: return EmitRotate(f, ValType::I64, op == Op::I64Rotl); case Op::I64Eqz: return EmitConversion(f, ValType::I64, ValType::I32); case Op::I64Clz: return EmitUnaryWithType(f, ValType::I64, MIRType::Int64); case Op::I64Ctz: return EmitUnaryWithType(f, ValType::I64, MIRType::Int64); case Op::I64Popcnt: return EmitUnaryWithType(f, ValType::I64, MIRType::Int64); case Op::I64Load8S: return EmitLoad(f, ValType::I64, Scalar::Int8); case Op::I64Load8U: return EmitLoad(f, ValType::I64, Scalar::Uint8); case Op::I64Load16S: return EmitLoad(f, ValType::I64, Scalar::Int16); case Op::I64Load16U: return EmitLoad(f, ValType::I64, Scalar::Uint16); case Op::I64Load32S: return EmitLoad(f, ValType::I64, Scalar::Int32); case Op::I64Load32U: return EmitLoad(f, ValType::I64, Scalar::Uint32); case Op::I64Load: return EmitLoad(f, ValType::I64, Scalar::Int64); case Op::I64Store8: return EmitStore(f, ValType::I64, Scalar::Int8); case Op::I64TeeStore8: return EmitTeeStore(f, ValType::I64, Scalar::Int8); case Op::I64Store16: return EmitStore(f, ValType::I64, Scalar::Int16); case Op::I64TeeStore16: return EmitTeeStore(f, ValType::I64, Scalar::Int16); case Op::I64Store32: return EmitStore(f, ValType::I64, Scalar::Int32); case Op::I64TeeStore32: return EmitTeeStore(f, ValType::I64, Scalar::Int32); case Op::I64Store: return EmitStore(f, ValType::I64, Scalar::Int64); case Op::I64TeeStore: return EmitTeeStore(f, ValType::I64, Scalar::Int64); // F32 case Op::F32Const: { RawF32 f32; if (!f.iter().readF32Const(&f32)) return false; f.iter().setResult(f.constant(f32)); return true; } case Op::F32Add: return EmitAdd(f, ValType::F32, MIRType::Float32); case Op::F32Sub: return EmitSub(f, ValType::F32, MIRType::Float32); case Op::F32Mul: return EmitMul(f, ValType::F32, MIRType::Float32); case Op::F32Div: return EmitDiv(f, ValType::F32, MIRType::Float32, /* isUnsigned = */ false); case Op::F32Min: case Op::F32Max: return EmitMinMax(f, ValType::F32, MIRType::Float32, op == Op::F32Max); case Op::F32CopySign: return EmitCopySign(f, ValType::F32); case Op::F32Neg: return EmitUnaryWithType(f, ValType::F32, MIRType::Float32); case Op::F32Abs: return EmitUnaryWithType(f, ValType::F32, MIRType::Float32); case Op::F32Sqrt: return EmitUnaryWithType(f, ValType::F32, MIRType::Float32); case Op::F32Ceil: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::CeilF, ValType::F32); case Op::F32Floor: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::FloorF, ValType::F32); case Op::F32Trunc: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::TruncF, ValType::F32); case Op::F32Nearest: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::NearbyIntF, ValType::F32); case Op::F32DemoteF64: return EmitConversion(f, ValType::F64, ValType::F32); case Op::F32ConvertSI32: return EmitConversion(f, ValType::I32, ValType::F32); case Op::F32ConvertUI32: return EmitConversion(f, ValType::I32, ValType::F32); case Op::F32ConvertSI64: case Op::F32ConvertUI64: return EmitConvertI64ToFloatingPoint(f, ValType::F32, MIRType::Float32, op == Op::F32ConvertUI64); case Op::F32ReinterpretI32: return EmitReinterpret(f, ValType::F32, ValType::I32, MIRType::Float32); case Op::F32Load: return EmitLoad(f, ValType::F32, Scalar::Float32); case Op::F32Store: return EmitStore(f, ValType::F32, Scalar::Float32); case Op::F32TeeStore: return EmitTeeStore(f, ValType::F32, Scalar::Float32); case Op::F32TeeStoreF64: return EmitTeeStoreWithCoercion(f, ValType::F32, Scalar::Float64); // F64 case Op::F64Const: { RawF64 f64; if (!f.iter().readF64Const(&f64)) return false; f.iter().setResult(f.constant(f64)); return true; } case Op::F64Add: return EmitAdd(f, ValType::F64, MIRType::Double); case Op::F64Sub: return EmitSub(f, ValType::F64, MIRType::Double); case Op::F64Mul: return EmitMul(f, ValType::F64, MIRType::Double); case Op::F64Div: return EmitDiv(f, ValType::F64, MIRType::Double, /* isUnsigned = */ false); case Op::F64Mod: return EmitRem(f, ValType::F64, MIRType::Double, /* isUnsigned = */ false); case Op::F64Min: case Op::F64Max: return EmitMinMax(f, ValType::F64, MIRType::Double, op == Op::F64Max); case Op::F64CopySign: return EmitCopySign(f, ValType::F64); case Op::F64Neg: return EmitUnaryWithType(f, ValType::F64, MIRType::Double); case Op::F64Abs: return EmitUnaryWithType(f, ValType::F64, MIRType::Double); case Op::F64Sqrt: return EmitUnaryWithType(f, ValType::F64, MIRType::Double); case Op::F64Ceil: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::CeilD, ValType::F64); case Op::F64Floor: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::FloorD, ValType::F64); case Op::F64Trunc: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::TruncD, ValType::F64); case Op::F64Nearest: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::NearbyIntD, ValType::F64); case Op::F64Sin: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::SinD, ValType::F64); case Op::F64Cos: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::CosD, ValType::F64); case Op::F64Tan: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::TanD, ValType::F64); case Op::F64Asin: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::ASinD, ValType::F64); case Op::F64Acos: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::ACosD, ValType::F64); case Op::F64Atan: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::ATanD, ValType::F64); case Op::F64Exp: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::ExpD, ValType::F64); case Op::F64Log: return EmitUnaryMathBuiltinCall(f, SymbolicAddress::LogD, ValType::F64); case Op::F64Pow: return EmitBinaryMathBuiltinCall(f, SymbolicAddress::PowD, ValType::F64); case Op::F64Atan2: return EmitBinaryMathBuiltinCall(f, SymbolicAddress::ATan2D, ValType::F64); case Op::F64PromoteF32: return EmitConversion(f, ValType::F32, ValType::F64); case Op::F64ConvertSI32: return EmitConversion(f, ValType::I32, ValType::F64); case Op::F64ConvertUI32: return EmitConversion(f, ValType::I32, ValType::F64); case Op::F64ConvertSI64: case Op::F64ConvertUI64: return EmitConvertI64ToFloatingPoint(f, ValType::F64, MIRType::Double, op == Op::F64ConvertUI64); case Op::F64Load: return EmitLoad(f, ValType::F64, Scalar::Float64); case Op::F64Store: return EmitStore(f, ValType::F64, Scalar::Float64); case Op::F64TeeStore: return EmitTeeStore(f, ValType::F64, Scalar::Float64); case Op::F64TeeStoreF32: return EmitTeeStoreWithCoercion(f, ValType::F64, Scalar::Float32); case Op::F64ReinterpretI64: return EmitReinterpret(f, ValType::F64, ValType::I64, MIRType::Double); // Comparisons case Op::I32Eq: return EmitComparison(f, ValType::I32, JSOP_EQ, MCompare::Compare_Int32); case Op::I32Ne: return EmitComparison(f, ValType::I32, JSOP_NE, MCompare::Compare_Int32); case Op::I32LtS: return EmitComparison(f, ValType::I32, JSOP_LT, MCompare::Compare_Int32); case Op::I32LeS: return EmitComparison(f, ValType::I32, JSOP_LE, MCompare::Compare_Int32); case Op::I32GtS: return EmitComparison(f, ValType::I32, JSOP_GT, MCompare::Compare_Int32); case Op::I32GeS: return EmitComparison(f, ValType::I32, JSOP_GE, MCompare::Compare_Int32); case Op::I32LtU: return EmitComparison(f, ValType::I32, JSOP_LT, MCompare::Compare_UInt32); case Op::I32LeU: return EmitComparison(f, ValType::I32, JSOP_LE, MCompare::Compare_UInt32); case Op::I32GtU: return EmitComparison(f, ValType::I32, JSOP_GT, MCompare::Compare_UInt32); case Op::I32GeU: return EmitComparison(f, ValType::I32, JSOP_GE, MCompare::Compare_UInt32); case Op::I64Eq: return EmitComparison(f, ValType::I64, JSOP_EQ, MCompare::Compare_Int64); case Op::I64Ne: return EmitComparison(f, ValType::I64, JSOP_NE, MCompare::Compare_Int64); case Op::I64LtS: return EmitComparison(f, ValType::I64, JSOP_LT, MCompare::Compare_Int64); case Op::I64LeS: return EmitComparison(f, ValType::I64, JSOP_LE, MCompare::Compare_Int64); case Op::I64GtS: return EmitComparison(f, ValType::I64, JSOP_GT, MCompare::Compare_Int64); case Op::I64GeS: return EmitComparison(f, ValType::I64, JSOP_GE, MCompare::Compare_Int64); case Op::I64LtU: return EmitComparison(f, ValType::I64, JSOP_LT, MCompare::Compare_UInt64); case Op::I64LeU: return EmitComparison(f, ValType::I64, JSOP_LE, MCompare::Compare_UInt64); case Op::I64GtU: return EmitComparison(f, ValType::I64, JSOP_GT, MCompare::Compare_UInt64); case Op::I64GeU: return EmitComparison(f, ValType::I64, JSOP_GE, MCompare::Compare_UInt64); case Op::F32Eq: return EmitComparison(f, ValType::F32, JSOP_EQ, MCompare::Compare_Float32); case Op::F32Ne: return EmitComparison(f, ValType::F32, JSOP_NE, MCompare::Compare_Float32); case Op::F32Lt: return EmitComparison(f, ValType::F32, JSOP_LT, MCompare::Compare_Float32); case Op::F32Le: return EmitComparison(f, ValType::F32, JSOP_LE, MCompare::Compare_Float32); case Op::F32Gt: return EmitComparison(f, ValType::F32, JSOP_GT, MCompare::Compare_Float32); case Op::F32Ge: return EmitComparison(f, ValType::F32, JSOP_GE, MCompare::Compare_Float32); case Op::F64Eq: return EmitComparison(f, ValType::F64, JSOP_EQ, MCompare::Compare_Double); case Op::F64Ne: return EmitComparison(f, ValType::F64, JSOP_NE, MCompare::Compare_Double); case Op::F64Lt: return EmitComparison(f, ValType::F64, JSOP_LT, MCompare::Compare_Double); case Op::F64Le: return EmitComparison(f, ValType::F64, JSOP_LE, MCompare::Compare_Double); case Op::F64Gt: return EmitComparison(f, ValType::F64, JSOP_GT, MCompare::Compare_Double); case Op::F64Ge: return EmitComparison(f, ValType::F64, JSOP_GE, MCompare::Compare_Double); // SIMD #define CASE(TYPE, OP, SIGN) \ case Op::TYPE##OP: \ return EmitSimdOp(f, ValType::TYPE, SimdOperation::Fn_##OP, SIGN); #define I8x16CASE(OP) CASE(I8x16, OP, SimdSign::Signed) #define I16x8CASE(OP) CASE(I16x8, OP, SimdSign::Signed) #define I32x4CASE(OP) CASE(I32x4, OP, SimdSign::Signed) #define F32x4CASE(OP) CASE(F32x4, OP, SimdSign::NotApplicable) #define B8x16CASE(OP) CASE(B8x16, OP, SimdSign::NotApplicable) #define B16x8CASE(OP) CASE(B16x8, OP, SimdSign::NotApplicable) #define B32x4CASE(OP) CASE(B32x4, OP, SimdSign::NotApplicable) #define ENUMERATE(TYPE, FORALL, DO) \ case Op::TYPE##Constructor: \ return EmitSimdOp(f, ValType::TYPE, SimdOperation::Constructor, SimdSign::NotApplicable); \ FORALL(DO) ENUMERATE(I8x16, FORALL_INT8X16_ASMJS_OP, I8x16CASE) ENUMERATE(I16x8, FORALL_INT16X8_ASMJS_OP, I16x8CASE) ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32x4CASE) ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32x4CASE) ENUMERATE(B8x16, FORALL_BOOL_SIMD_OP, B8x16CASE) ENUMERATE(B16x8, FORALL_BOOL_SIMD_OP, B16x8CASE) ENUMERATE(B32x4, FORALL_BOOL_SIMD_OP, B32x4CASE) #undef CASE #undef I8x16CASE #undef I16x8CASE #undef I32x4CASE #undef F32x4CASE #undef B8x16CASE #undef B16x8CASE #undef B32x4CASE #undef ENUMERATE case Op::I8x16Const: { I8x16 i8x16; if (!f.iter().readI8x16Const(&i8x16)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX16(i8x16), MIRType::Int8x16)); return true; } case Op::I16x8Const: { I16x8 i16x8; if (!f.iter().readI16x8Const(&i16x8)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX8(i16x8), MIRType::Int16x8)); return true; } case Op::I32x4Const: { I32x4 i32x4; if (!f.iter().readI32x4Const(&i32x4)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX4(i32x4), MIRType::Int32x4)); return true; } case Op::F32x4Const: { F32x4 f32x4; if (!f.iter().readF32x4Const(&f32x4)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX4(f32x4), MIRType::Float32x4)); return true; } case Op::B8x16Const: { I8x16 i8x16; if (!f.iter().readB8x16Const(&i8x16)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX16(i8x16), MIRType::Bool8x16)); return true; } case Op::B16x8Const: { I16x8 i16x8; if (!f.iter().readB16x8Const(&i16x8)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX8(i16x8), MIRType::Bool16x8)); return true; } case Op::B32x4Const: { I32x4 i32x4; if (!f.iter().readB32x4Const(&i32x4)) return false; f.iter().setResult(f.constant(SimdConstant::CreateX4(i32x4), MIRType::Bool32x4)); return true; } // SIMD unsigned integer operations. case Op::I8x16addSaturateU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_addSaturate, SimdSign::Unsigned); case Op::I8x16subSaturateU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_subSaturate, SimdSign::Unsigned); case Op::I8x16shiftRightByScalarU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_shiftRightByScalar, SimdSign::Unsigned); case Op::I8x16lessThanU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_lessThan, SimdSign::Unsigned); case Op::I8x16lessThanOrEqualU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_lessThanOrEqual, SimdSign::Unsigned); case Op::I8x16greaterThanU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_greaterThan, SimdSign::Unsigned); case Op::I8x16greaterThanOrEqualU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_greaterThanOrEqual, SimdSign::Unsigned); case Op::I8x16extractLaneU: return EmitSimdOp(f, ValType::I8x16, SimdOperation::Fn_extractLane, SimdSign::Unsigned); case Op::I16x8addSaturateU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_addSaturate, SimdSign::Unsigned); case Op::I16x8subSaturateU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_subSaturate, SimdSign::Unsigned); case Op::I16x8shiftRightByScalarU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_shiftRightByScalar, SimdSign::Unsigned); case Op::I16x8lessThanU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_lessThan, SimdSign::Unsigned); case Op::I16x8lessThanOrEqualU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_lessThanOrEqual, SimdSign::Unsigned); case Op::I16x8greaterThanU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_greaterThan, SimdSign::Unsigned); case Op::I16x8greaterThanOrEqualU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_greaterThanOrEqual, SimdSign::Unsigned); case Op::I16x8extractLaneU: return EmitSimdOp(f, ValType::I16x8, SimdOperation::Fn_extractLane, SimdSign::Unsigned); case Op::I32x4shiftRightByScalarU: return EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_shiftRightByScalar, SimdSign::Unsigned); case Op::I32x4lessThanU: return EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_lessThan, SimdSign::Unsigned); case Op::I32x4lessThanOrEqualU: return EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_lessThanOrEqual, SimdSign::Unsigned); case Op::I32x4greaterThanU: return EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_greaterThan, SimdSign::Unsigned); case Op::I32x4greaterThanOrEqualU: return EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_greaterThanOrEqual, SimdSign::Unsigned); case Op::I32x4fromFloat32x4U: return EmitSimdOp(f, ValType::I32x4, SimdOperation::Fn_fromFloat32x4, SimdSign::Unsigned); // Atomics case Op::I32AtomicsLoad: return EmitAtomicsLoad(f); case Op::I32AtomicsStore: return EmitAtomicsStore(f); case Op::I32AtomicsBinOp: return EmitAtomicsBinOp(f); case Op::I32AtomicsCompareExchange: return EmitAtomicsCompareExchange(f); case Op::I32AtomicsExchange: return EmitAtomicsExchange(f); // Memory Operators case Op::GrowMemory: return EmitGrowMemory(f); case Op::CurrentMemory: return EmitCurrentMemory(f); case Op::Limit:; } MOZ_CRASH("unexpected wasm opcode"); } bool wasm::IonCompileFunction(IonCompileTask* task) { MOZ_ASSERT(task->mode() == IonCompileTask::CompileMode::Ion); const FuncBytes& func = task->func(); FuncCompileResults& results = task->results(); Decoder d(func.bytes()); // Build the local types vector. ValTypeVector locals; if (!locals.appendAll(func.sig().args())) return false; if (!DecodeLocalEntries(d, task->mg().kind, &locals)) return false; // Set up for Ion compilation. JitContext jitContext(&results.alloc()); const JitCompileOptions options; MIRGraph graph(&results.alloc()); CompileInfo compileInfo(locals.length()); MIRGenerator mir(nullptr, options, &results.alloc(), &graph, &compileInfo, IonOptimizations.get(OptimizationLevel::Wasm)); mir.initMinWasmHeapLength(task->mg().minMemoryLength); // Capture the prologue's trap site before decoding the function. TrapOffset prologueTrapOffset; // Build MIR graph { FunctionCompiler f(task->mg(), d, func, locals, mir, results); if (!f.init()) return false; prologueTrapOffset = f.iter().trapOffset(); if (!f.startBlock()) return false; if (!f.iter().readFunctionStart(f.sig().ret())) return false; while (!f.done()) { if (!EmitExpr(f)) return false; } if (f.inDeadCode() || IsVoid(f.sig().ret())) f.returnVoid(); else f.returnExpr(f.iter().getResult()); if (!f.iter().readFunctionEnd()) return false; f.finish(); } // Compile MIR graph { jit::SpewBeginFunction(&mir, nullptr); jit::AutoSpewEndFunction spewEndFunction(&mir); if (!OptimizeMIR(&mir)) return false; LIRGraph* lir = GenerateLIR(&mir); if (!lir) return false; SigIdDesc sigId = task->mg().funcSigs[func.index()]->id; CodeGenerator codegen(&mir, lir, &results.masm()); if (!codegen.generateWasm(sigId, prologueTrapOffset, &results.offsets())) return false; } return true; } bool wasm::CompileFunction(IonCompileTask* task) { TraceLoggerThread* logger = TraceLoggerForCurrentThread(); AutoTraceLog logCompile(logger, TraceLogger_WasmCompilation); switch (task->mode()) { case wasm::IonCompileTask::CompileMode::Ion: return wasm::IonCompileFunction(task); case wasm::IonCompileTask::CompileMode::Baseline: return wasm::BaselineCompileFunction(task); case wasm::IonCompileTask::CompileMode::None: break; } MOZ_CRASH("Uninitialized task"); }