Files
palemoon27/js/src/asmjs/WasmIonCompile.cpp
T
roytam1 fc9fb8b315 import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1240411: P6. Clean up OMX headers. r=ayang (114a1df4be)
- Bug 1230385, part 1 - Use MOZ_CRASH in ContentChild::AllocP*() methods. r=billm (6f8016bb29)
- Bug 1230385, part 2 - Use NS_WARNING in unimplemented TabChild methods. r=billm (28a7165773)
- Bug 1234026 - Pass a --display option to gtk_init in content processes. r=karlt (8e3e17858e)
- Bug 1218816 - Remove useless semicolons. Found by coccinelle. r=Ehsan (e029c51e58)
- Bug 1240796 - Implement Uint32x4 <==> Float32x4 conversions. r=sunfish (5a42f571ea)
- Bug 1240796 - Implement unsigned SIMD compares. r=sunfish (93d4979730)
- Bug 1240796 - Implement Uint32x4 extractLane in Ion. r=nbp (b5b7c782b6)
- Bug 1240796 - Add Uint32x4 support to jit-test/lib/simd.js. r=bbouvier (187cbbb1d0)
- Bug 1245547 - Implement RSimdBox for Uint32x4. r=nbp (0186f0355f)
- Bug 1244254 - Move SimdTypeToMIRType into the header. r=nbp (5b15375c4e)
- Bug 1217236 - Block trackers loaded by Flash movies. r=gcp (a1318a33da)
- Bug 1237402 - Allow certain plugins to be loaded in parent process (r=jimm) (e951737778)
- Bug 1172304 - Fix to handle short read in Plugin code. r=johns (625dadd61b)
- Bug 377630 - Preventing filename disclosure, by putting downloaded files in a private directory. r=bz (5aca752a5c)
- Bug 579517 follow-up: Remove NSPR types that crept in (c7cb9ffc11)
- Bug 1245724 - Make plugin network requests bypass service worker interception. r=ehsan (a0b7fab3ee)
- Please enter the commit message for your chang Bug 1244254 - Pass a SimdType to inlineSimd(). r=nbp (6fcacd3c5d)
- Bug 1244254 - Add IonBuilder::unboxSimd(). r=nbp (d5f0922fd9)
- Bug 1244254 - Check SIMD arguments in IonBuilder. r=nbp (832d38940f)
- Bug 1244254 - Replace MaybeSimdUnbox with assertions. r=nbp (9c2ad4a8c5)
- Bug 1244254 - Add SimdType to MSimdBox and MSimdUnbox. r=nbp (74412371f5)
- Bug 1238003 - Part 1: Add BooleanPolicy. r=jandem (79c1716cac)
- Bug 1238003 - Part 2: Use Policy in RegExpMatcher and RegExpTester. r=jandem (a2ef8feec4)
- Bug 1238003 - Part 3: Add test for Policy in RegExpMatcher and RegExpTester. r=jandem (1653bec783)
- Bug 1244254 - Simplify MSimd* constructors. r=nbp (0ab2efcb4c)
- Bug 1244889 - Remove trivial SIMD NewAsmJS factories. r=bbouvier (2f9c41713c)
- Bug 1244828 - Ensure enough ballast space in CallPolicy::adjustInputs. r=bbouvier (d84dae2175)
- Bug 1244828 - Ensure enough ballast space in AllDoublePolicy::adjustInputs. r=bbouvier (361092db86)
- Bug 1245421: Remove dead function CoercesToDouble; r=h4writer (502b9efcee)
2023-09-11 09:34:11 +08:00

3048 lines
95 KiB
C++

/* -*- 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 "asmjs/WasmIonCompile.h"
#include "asmjs/WasmGenerator.h"
#include "jit/CodeGenerator.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::DebugOnly;
using mozilla::Maybe;
typedef Vector<size_t, 1, SystemAllocPolicy> LabelVector;
typedef Vector<MBasicBlock*, 8, SystemAllocPolicy> BlockVector;
// 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
{
private:
typedef HashMap<uint32_t, BlockVector, DefaultHasher<uint32_t>, SystemAllocPolicy> LabeledBlockMap;
typedef HashMap<size_t, BlockVector, DefaultHasher<uint32_t>, SystemAllocPolicy> UnlabeledBlockMap;
typedef Vector<size_t, 4, SystemAllocPolicy> PositionStack;
ModuleGeneratorThreadView& mg_;
const FuncBytecode& func_;
Decoder decoder_;
size_t nextId_;
size_t lastReadCallSite_;
TempAllocator& alloc_;
MIRGraph& graph_;
const CompileInfo& info_;
MIRGenerator& mirGen_;
MBasicBlock* curBlock_;
PositionStack loopStack_;
PositionStack breakableStack_;
UnlabeledBlockMap unlabeledBreaks_;
UnlabeledBlockMap unlabeledContinues_;
LabeledBlockMap labeledBreaks_;
LabeledBlockMap labeledContinues_;
FuncCompileResults& compileResults_;
public:
FunctionCompiler(ModuleGeneratorThreadView& mg, const FuncBytecode& func, MIRGenerator& mirGen,
FuncCompileResults& compileResults)
: mg_(mg),
func_(func),
decoder_(func.bytecode()),
nextId_(0),
lastReadCallSite_(0),
alloc_(mirGen.alloc()),
graph_(mirGen.graph()),
info_(mirGen.info()),
mirGen_(mirGen),
curBlock_(nullptr),
compileResults_(compileResults)
{}
ModuleGeneratorThreadView& mg() const { return mg_; }
TempAllocator& alloc() const { return alloc_; }
MacroAssembler& masm() const { return compileResults_.masm(); }
const Sig& sig() const { return func_.sig(); }
bool init()
{
if (!unlabeledBreaks_.init() ||
!unlabeledContinues_.init() ||
!labeledBreaks_.init() ||
!labeledContinues_.init())
{
return false;
}
// Prepare the entry block for MIR generation:
const ValTypeVector& args = func_.sig().args();
if (!mirGen_.ensureBallast())
return false;
if (!newBlock(/* pred = */ nullptr, &curBlock_))
return false;
for (ABIArgValTypeIter i(args); !i.done(); i++) {
MAsmJSParameter* ins = MAsmJSParameter::New(alloc(), *i, i.mirType());
curBlock_->add(ins);
curBlock_->initSlot(info().localSlot(i.index()), ins);
if (!mirGen_.ensureBallast())
return false;
}
for (size_t i = args.length(); i < func_.numLocals(); i++) {
MInstruction* ins = nullptr;
switch (func_.localType(i)) {
case ValType::I32:
ins = MConstant::NewAsmJS(alloc(), Int32Value(0), MIRType_Int32);
break;
case ValType::I64:
MOZ_CRASH("int64");
case ValType::F32:
ins = MConstant::NewAsmJS(alloc(), Float32Value(0.f), MIRType_Float32);
break;
case ValType::F64:
ins = MConstant::NewAsmJS(alloc(), DoubleValue(0.0), MIRType_Double);
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::B32x4:
// Bool32x4 uses the same data layout as Int32x4.
ins = MSimdConstant::New(alloc(), SimdConstant::SplatX4(0), MIRType_Bool32x4);
break;
case ValType::Limit:
MOZ_CRASH("Limit");
}
curBlock_->add(ins);
curBlock_->initSlot(info().localSlot(i), ins);
if (!mirGen_.ensureBallast())
return false;
}
return true;
}
void checkPostconditions()
{
MOZ_ASSERT(loopStack_.empty());
MOZ_ASSERT(unlabeledBreaks_.empty());
MOZ_ASSERT(unlabeledContinues_.empty());
MOZ_ASSERT(labeledBreaks_.empty());
MOZ_ASSERT(labeledContinues_.empty());
MOZ_ASSERT(inDeadCode());
MOZ_ASSERT(decoder_.done(), "all bytecode 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));
}
ExprType localType(unsigned slot) const { return ToExprType(func_.localType(slot)); }
/***************************** 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(Value v, MIRType type)
{
if (inDeadCode())
return nullptr;
MConstant* constant = MConstant::NewAsmJS(alloc(), v, type);
curBlock_->add(constant);
return constant;
}
template <class T>
MDefinition* unary(MDefinition* op)
{
if (inDeadCode())
return nullptr;
T* ins = T::NewAsmJS(alloc(), op);
curBlock_->add(ins);
return ins;
}
template <class T>
MDefinition* unary(MDefinition* op, MIRType type)
{
if (inDeadCode())
return nullptr;
T* ins = T::NewAsmJS(alloc(), op, type);
curBlock_->add(ins);
return ins;
}
template <class T>
MDefinition* binary(MDefinition* lhs, MDefinition* rhs)
{
if (inDeadCode())
return nullptr;
T* ins = T::New(alloc(), lhs, rhs);
curBlock_->add(ins);
return ins;
}
template <class T>
MDefinition* binary(MDefinition* lhs, MDefinition* rhs, MIRType type)
{
if (inDeadCode())
return nullptr;
T* ins = T::NewAsmJS(alloc(), lhs, rhs, 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);
MSimdBinaryArith* ins = MSimdBinaryArith::NewAsmJS(alloc(), lhs, rhs, op);
curBlock_->add(ins);
return ins;
}
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);
MSimdBinaryBitwise* ins = MSimdBinaryBitwise::NewAsmJS(alloc(), lhs, rhs, op);
curBlock_->add(ins);
return ins;
}
template<class T>
MDefinition* binarySimd(MDefinition* lhs, MDefinition* rhs, typename T::Operation op)
{
if (inDeadCode())
return nullptr;
T* ins = T::NewAsmJS(alloc(), lhs, rhs, op);
curBlock_->add(ins);
return ins;
}
MDefinition* swizzleSimd(MDefinition* vector, int32_t X, int32_t Y, int32_t Z, int32_t W,
MIRType type)
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(vector->type() == type);
MSimdSwizzle* ins = MSimdSwizzle::New(alloc(), vector, X, Y, Z, W);
curBlock_->add(ins);
return ins;
}
MDefinition* shuffleSimd(MDefinition* lhs, MDefinition* rhs, int32_t X, int32_t Y,
int32_t Z, int32_t W, MIRType type)
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(lhs->type() == type);
MInstruction* ins = MSimdShuffle::New(alloc(), lhs, rhs, X, Y, Z, W);
curBlock_->add(ins);
return ins;
}
MDefinition* insertElementSimd(MDefinition* vec, MDefinition* val, SimdLane 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;
}
template<class T>
MDefinition* convertSimd(MDefinition* vec, MIRType from, MIRType to)
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(vec->type() == from);
MOZ_ASSERT(IsSimdType(from) && IsSimdType(to) && from != to);
T* ins = T::NewAsmJS(alloc(), vec, to);
curBlock_->add(ins);
return ins;
}
MDefinition* splatSimd(MDefinition* v, MIRType type)
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(IsSimdType(type));
MOZ_ASSERT(SimdTypeToLaneArgumentType(type) == v->type());
MSimdSplatX4* ins = MSimdSplatX4::New(alloc(), v, type);
curBlock_->add(ins);
return ins;
}
MDefinition* minMax(MDefinition* lhs, MDefinition* rhs, MIRType type, bool isMax) {
if (inDeadCode())
return nullptr;
MMinMax* ins = MMinMax::New(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;
MMul* ins = MMul::New(alloc(), lhs, rhs, type, mode);
curBlock_->add(ins);
return ins;
}
MDefinition* div(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd)
{
if (inDeadCode())
return nullptr;
MDiv* ins = MDiv::NewAsmJS(alloc(), lhs, rhs, type, unsignd);
curBlock_->add(ins);
return ins;
}
MDefinition* mod(MDefinition* lhs, MDefinition* rhs, MIRType type, bool unsignd)
{
if (inDeadCode())
return nullptr;
MMod* ins = MMod::NewAsmJS(alloc(), lhs, rhs, type, unsignd);
curBlock_->add(ins);
return ins;
}
template <class T>
MDefinition* bitwise(MDefinition* lhs, MDefinition* rhs)
{
if (inDeadCode())
return nullptr;
T* ins = T::NewAsmJS(alloc(), lhs, rhs);
curBlock_->add(ins);
return ins;
}
template <class T>
MDefinition* bitwise(MDefinition* op)
{
if (inDeadCode())
return nullptr;
T* ins = T::NewAsmJS(alloc(), op);
curBlock_->add(ins);
return ins;
}
MDefinition* compare(MDefinition* lhs, MDefinition* rhs, JSOp op, MCompare::CompareType type)
{
if (inDeadCode())
return nullptr;
MCompare* ins = MCompare::NewAsmJS(alloc(), lhs, rhs, op, type);
curBlock_->add(ins);
return ins;
}
void assign(unsigned slot, MDefinition* def)
{
if (inDeadCode())
return;
curBlock_->setSlot(info().localSlot(slot), def);
}
MDefinition* loadHeap(Scalar::Type accessType, MDefinition* ptr, NeedsBoundsCheck chk)
{
if (inDeadCode())
return nullptr;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD loads should use loadSimdHeap");
MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), accessType, ptr, needsBoundsCheck);
curBlock_->add(load);
return load;
}
MDefinition* loadSimdHeap(Scalar::Type accessType, MDefinition* ptr, NeedsBoundsCheck chk,
unsigned numElems)
{
if (inDeadCode())
return nullptr;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MOZ_ASSERT(Scalar::isSimdType(accessType), "loadSimdHeap can only load from a SIMD view");
MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), accessType, ptr, needsBoundsCheck,
numElems);
curBlock_->add(load);
return load;
}
void storeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v, NeedsBoundsCheck chk)
{
if (inDeadCode())
return;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MOZ_ASSERT(!Scalar::isSimdType(accessType), "SIMD stores should use storeSimdHeap");
MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), accessType, ptr, v, needsBoundsCheck);
curBlock_->add(store);
}
void storeSimdHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v,
NeedsBoundsCheck chk, unsigned numElems)
{
if (inDeadCode())
return;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MOZ_ASSERT(Scalar::isSimdType(accessType), "storeSimdHeap can only load from a SIMD view");
MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), accessType, ptr, v, needsBoundsCheck,
numElems);
curBlock_->add(store);
}
void memoryBarrier(MemoryBarrierBits type)
{
if (inDeadCode())
return;
MMemoryBarrier* ins = MMemoryBarrier::New(alloc(), type);
curBlock_->add(ins);
}
MDefinition* atomicLoadHeap(Scalar::Type accessType, MDefinition* ptr, NeedsBoundsCheck chk)
{
if (inDeadCode())
return nullptr;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MAsmJSLoadHeap* load = MAsmJSLoadHeap::New(alloc(), accessType, ptr, needsBoundsCheck,
/* numElems */ 0,
MembarBeforeLoad, MembarAfterLoad);
curBlock_->add(load);
return load;
}
void atomicStoreHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* v, NeedsBoundsCheck chk)
{
if (inDeadCode())
return;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MAsmJSStoreHeap* store = MAsmJSStoreHeap::New(alloc(), accessType, ptr, v, needsBoundsCheck,
/* numElems = */ 0,
MembarBeforeStore, MembarAfterStore);
curBlock_->add(store);
}
MDefinition* atomicCompareExchangeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* oldv,
MDefinition* newv, NeedsBoundsCheck chk)
{
if (inDeadCode())
return nullptr;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MAsmJSCompareExchangeHeap* cas =
MAsmJSCompareExchangeHeap::New(alloc(), accessType, ptr, oldv, newv, needsBoundsCheck);
curBlock_->add(cas);
return cas;
}
MDefinition* atomicExchangeHeap(Scalar::Type accessType, MDefinition* ptr, MDefinition* value,
NeedsBoundsCheck chk)
{
if (inDeadCode())
return nullptr;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MAsmJSAtomicExchangeHeap* cas =
MAsmJSAtomicExchangeHeap::New(alloc(), accessType, ptr, value, needsBoundsCheck);
curBlock_->add(cas);
return cas;
}
MDefinition* atomicBinopHeap(js::jit::AtomicOp op, Scalar::Type accessType, MDefinition* ptr,
MDefinition* v, NeedsBoundsCheck chk)
{
if (inDeadCode())
return nullptr;
bool needsBoundsCheck = chk == NEEDS_BOUNDS_CHECK;
MAsmJSAtomicBinopHeap* binop =
MAsmJSAtomicBinopHeap::New(alloc(), op, accessType, ptr, v, needsBoundsCheck);
curBlock_->add(binop);
return binop;
}
MDefinition* loadGlobalVar(unsigned globalDataOffset, bool isConst, MIRType type)
{
if (inDeadCode())
return nullptr;
MAsmJSLoadGlobalVar* load = MAsmJSLoadGlobalVar::New(alloc(), type, globalDataOffset,
isConst);
curBlock_->add(load);
return load;
}
void storeGlobalVar(uint32_t globalDataOffset, MDefinition* v)
{
if (inDeadCode())
return;
curBlock_->add(MAsmJSStoreGlobalVar::New(alloc(), globalDataOffset, v));
}
void addInterruptCheck()
{
if (inDeadCode())
return;
// WasmHandleExecutionInterrupt takes 0 arguments and the stack is
// always ABIStackAlignment-aligned, but don't forget to account for
// ShadowStackSpace and any other ABI warts.
ABIArgGenerator abi;
if (abi.stackBytesConsumedSoFar() > mirGen_.maxAsmJSStackArgBytes())
mirGen_.setAsmJSMaxStackArgBytes(abi.stackBytesConsumedSoFar());
CallSiteDesc callDesc(0, CallSiteDesc::Relative);
curBlock_->add(MAsmJSInterruptCheck::New(alloc()));
}
MDefinition* extractSimdElement(SimdLane lane, MDefinition* base, MIRType type)
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(IsSimdType(base->type()));
MOZ_ASSERT(!IsSimdType(type));
MSimdExtractElement* ins = MSimdExtractElement::NewAsmJS(alloc(), base, type, lane);
curBlock_->add(ins);
return ins;
}
template<typename T>
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::maxAsmJSStackArgBytes.) 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.
class Call
{
uint32_t lineOrBytecode_;
ABIArgGenerator abi_;
uint32_t prevMaxStackBytes_;
uint32_t maxChildStackBytes_;
uint32_t spIncrement_;
MAsmJSCall::Args regArgs_;
Vector<MAsmJSPassStackArg*, 0, SystemAllocPolicy> stackArgs_;
bool childClobbers_;
friend class FunctionCompiler;
public:
Call(FunctionCompiler& f, uint32_t lineOrBytecode)
: lineOrBytecode_(lineOrBytecode),
prevMaxStackBytes_(0),
maxChildStackBytes_(0),
spIncrement_(0),
childClobbers_(false)
{ }
};
void startCallArgs(Call* call)
{
if (inDeadCode())
return;
call->prevMaxStackBytes_ = mirGen().resetAsmJSMaxStackArgBytes();
}
bool passArg(MDefinition* argDef, ValType type, Call* call)
{
if (inDeadCode())
return true;
uint32_t childStackBytes = mirGen().resetAsmJSMaxStackArgBytes();
call->maxChildStackBytes_ = Max(call->maxChildStackBytes_, childStackBytes);
if (childStackBytes > 0 && !call->stackArgs_.empty())
call->childClobbers_ = true;
ABIArg arg = call->abi_.next(ToMIRType(type));
if (arg.kind() == ABIArg::Stack) {
MAsmJSPassStackArg* mir = MAsmJSPassStackArg::New(alloc(), arg.offsetFromArgBase(),
argDef);
curBlock_->add(mir);
if (!call->stackArgs_.append(mir))
return false;
} else {
if (!call->regArgs_.append(MAsmJSCall::Arg(arg.reg(), argDef)))
return false;
}
return true;
}
void finishCallArgs(Call* call)
{
if (inDeadCode())
return;
uint32_t parentStackBytes = call->abi_.stackBytesConsumedSoFar();
uint32_t newStackBytes;
if (call->childClobbers_) {
call->spIncrement_ = AlignBytes(call->maxChildStackBytes_, AsmJSStackAlignment);
for (unsigned i = 0; i < call->stackArgs_.length(); i++)
call->stackArgs_[i]->incrementOffset(call->spIncrement_);
newStackBytes = Max(call->prevMaxStackBytes_,
call->spIncrement_ + parentStackBytes);
} else {
call->spIncrement_ = 0;
newStackBytes = Max(call->prevMaxStackBytes_,
Max(call->maxChildStackBytes_, parentStackBytes));
}
mirGen_.setAsmJSMaxStackArgBytes(newStackBytes);
}
private:
bool callPrivate(MAsmJSCall::Callee callee, const Call& call, ExprType ret, MDefinition** def)
{
if (inDeadCode()) {
*def = nullptr;
return true;
}
CallSiteDesc::Kind kind = CallSiteDesc::Kind(-1);
switch (callee.which()) {
case MAsmJSCall::Callee::Internal: kind = CallSiteDesc::Relative; break;
case MAsmJSCall::Callee::Dynamic: kind = CallSiteDesc::Register; break;
case MAsmJSCall::Callee::Builtin: kind = CallSiteDesc::Register; break;
}
MAsmJSCall* ins = MAsmJSCall::New(alloc(), CallSiteDesc(call.lineOrBytecode_, kind),
callee, call.regArgs_, ToMIRType(ret), call.spIncrement_);
if (!ins)
return false;
curBlock_->add(ins);
*def = ins;
return true;
}
public:
bool internalCall(const Sig& sig, uint32_t funcIndex, const Call& call, MDefinition** def)
{
return callPrivate(MAsmJSCall::Callee(AsmJSInternalCallee(funcIndex)), call, sig.ret(), def);
}
bool funcPtrCall(const Sig& sig, uint32_t maskLit, uint32_t globalDataOffset, MDefinition* index,
const Call& call, MDefinition** def)
{
if (inDeadCode()) {
*def = nullptr;
return true;
}
MConstant* mask = MConstant::New(alloc(), Int32Value(maskLit));
curBlock_->add(mask);
MBitAnd* maskedIndex = MBitAnd::NewAsmJS(alloc(), index, mask);
curBlock_->add(maskedIndex);
MAsmJSLoadFuncPtr* ptrFun = MAsmJSLoadFuncPtr::New(alloc(), globalDataOffset, maskedIndex);
curBlock_->add(ptrFun);
return callPrivate(MAsmJSCall::Callee(ptrFun), call, sig.ret(), def);
}
bool ffiCall(unsigned globalDataOffset, const Call& call, ExprType ret, MDefinition** def)
{
if (inDeadCode()) {
*def = nullptr;
return true;
}
MAsmJSLoadFFIFunc* ptrFun = MAsmJSLoadFFIFunc::New(alloc(), globalDataOffset);
curBlock_->add(ptrFun);
return callPrivate(MAsmJSCall::Callee(ptrFun), call, ret, def);
}
bool builtinCall(SymbolicAddress builtin, const Call& call, ValType type, MDefinition** def)
{
return callPrivate(MAsmJSCall::Callee(builtin), call, ToExprType(type), def);
}
/*********************************************** Control flow generation */
inline bool inDeadCode() const {
return curBlock_ == nullptr;
}
void returnExpr(MDefinition* expr)
{
if (inDeadCode())
return;
MAsmJSReturn* ins = MAsmJSReturn::New(alloc(), expr);
curBlock_->end(ins);
curBlock_ = nullptr;
}
void returnVoid()
{
if (inDeadCode())
return;
MAsmJSVoidReturn* ins = MAsmJSVoidReturn::New(alloc());
curBlock_->end(ins);
curBlock_ = nullptr;
}
bool branchAndStartThen(MDefinition* cond, MBasicBlock** thenBlock, MBasicBlock** elseBlock)
{
if (inDeadCode())
return true;
bool hasThenBlock = *thenBlock != nullptr;
bool hasElseBlock = *elseBlock != nullptr;
if (!hasThenBlock && !newBlock(curBlock_, thenBlock))
return false;
if (!hasElseBlock && !newBlock(curBlock_, elseBlock))
return false;
curBlock_->end(MTest::New(alloc(), cond, *thenBlock, *elseBlock));
// Only add as a predecessor if newBlock hasn't been called (as it does it for us)
if (hasThenBlock && !(*thenBlock)->addPredecessor(alloc(), curBlock_))
return false;
if (hasElseBlock && !(*elseBlock)->addPredecessor(alloc(), curBlock_))
return false;
curBlock_ = *thenBlock;
mirGraph().moveBlockToEnd(curBlock_);
return true;
}
void assertCurrentBlockIs(MBasicBlock* block) {
if (inDeadCode())
return;
MOZ_ASSERT(curBlock_ == block);
}
bool appendThenBlock(BlockVector* thenBlocks)
{
if (inDeadCode())
return true;
return thenBlocks->append(curBlock_);
}
bool joinIf(const BlockVector& thenBlocks, MBasicBlock* joinBlock)
{
if (!joinBlock)
return true;
MOZ_ASSERT_IF(curBlock_, thenBlocks.back() == curBlock_);
for (size_t i = 0; i < thenBlocks.length(); i++) {
thenBlocks[i]->end(MGoto::New(alloc(), joinBlock));
if (!joinBlock->addPredecessor(alloc(), thenBlocks[i]))
return false;
}
curBlock_ = joinBlock;
mirGraph().moveBlockToEnd(curBlock_);
return true;
}
void switchToElse(MBasicBlock* elseBlock)
{
if (!elseBlock)
return;
curBlock_ = elseBlock;
mirGraph().moveBlockToEnd(curBlock_);
}
bool joinIfElse(const BlockVector& thenBlocks)
{
if (inDeadCode() && thenBlocks.empty())
return true;
MBasicBlock* pred = curBlock_ ? curBlock_ : thenBlocks[0];
MBasicBlock* join;
if (!newBlock(pred, &join))
return false;
if (curBlock_)
curBlock_->end(MGoto::New(alloc(), join));
for (size_t i = 0; i < thenBlocks.length(); i++) {
thenBlocks[i]->end(MGoto::New(alloc(), join));
if (pred == curBlock_ || i > 0) {
if (!join->addPredecessor(alloc(), thenBlocks[i]))
return false;
}
}
curBlock_ = join;
return true;
}
bool usesPhiSlot()
{
return curBlock_->stackDepth() == info().firstStackSlot() + 1;
}
void pushPhiInput(MDefinition* def)
{
if (inDeadCode())
return;
MOZ_ASSERT(!usesPhiSlot());
curBlock_->push(def);
}
MDefinition* popPhiOutput()
{
if (inDeadCode())
return nullptr;
MOZ_ASSERT(usesPhiSlot());
return curBlock_->pop();
}
bool startPendingLoop(size_t id, MBasicBlock** loopEntry)
{
if (!loopStack_.append(id) || !breakableStack_.append(id))
return false;
if (inDeadCode()) {
*loopEntry = nullptr;
return true;
}
MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() - 1);
*loopEntry = MBasicBlock::NewAsmJS(mirGraph(), info(), curBlock_,
MBasicBlock::PENDING_LOOP_HEADER);
if (!*loopEntry)
return false;
mirGraph().addBlock(*loopEntry);
(*loopEntry)->setLoopDepth(loopStack_.length());
curBlock_->end(MGoto::New(alloc(), *loopEntry));
curBlock_ = *loopEntry;
return true;
}
bool branchAndStartLoopBody(MDefinition* cond, MBasicBlock** afterLoop)
{
if (inDeadCode()) {
*afterLoop = nullptr;
return true;
}
MOZ_ASSERT(curBlock_->loopDepth() > 0);
MBasicBlock* body;
if (!newBlock(curBlock_, &body))
return false;
if (cond->isConstant() && cond->toConstant()->valueToBoolean()) {
*afterLoop = nullptr;
curBlock_->end(MGoto::New(alloc(), body));
} else {
if (!newBlockWithDepth(curBlock_, curBlock_->loopDepth() - 1, afterLoop))
return false;
curBlock_->end(MTest::New(alloc(), cond, body, *afterLoop));
}
curBlock_ = body;
return true;
}
private:
size_t popLoop()
{
size_t id = loopStack_.popCopy();
MOZ_ASSERT(!unlabeledContinues_.has(id));
breakableStack_.popBack();
return id;
}
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));
}
}
template <typename T>
void fixupRedundantPhis(MBasicBlock* loopEntry, T& map)
{
if (!map.initialized())
return;
for (typename T::Enum e(map); !e.empty(); e.popFront()) {
BlockVector& blocks = e.front().value();
for (size_t i = 0; i < blocks.length(); i++) {
if (blocks[i]->loopDepth() >= loopEntry->loopDepth())
fixupRedundantPhis(blocks[i]);
}
}
}
bool setLoopBackedge(MBasicBlock* loopEntry, MBasicBlock* backedge, MBasicBlock* afterLoop)
{
if (!loopEntry->setBackedgeAsmJS(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.
if (afterLoop)
fixupRedundantPhis(afterLoop);
fixupRedundantPhis(loopEntry, labeledContinues_);
fixupRedundantPhis(loopEntry, labeledBreaks_);
fixupRedundantPhis(loopEntry, unlabeledContinues_);
fixupRedundantPhis(loopEntry, unlabeledBreaks_);
// 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* loopEntry, MBasicBlock* afterLoop)
{
size_t id = popLoop();
if (!loopEntry) {
MOZ_ASSERT(!afterLoop);
MOZ_ASSERT(inDeadCode());
MOZ_ASSERT(!unlabeledBreaks_.has(id));
return true;
}
MOZ_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1);
MOZ_ASSERT_IF(afterLoop, afterLoop->loopDepth() == loopStack_.length());
if (curBlock_) {
MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1);
curBlock_->end(MGoto::New(alloc(), loopEntry));
if (!setLoopBackedge(loopEntry, curBlock_, afterLoop))
return false;
}
curBlock_ = afterLoop;
if (curBlock_)
mirGraph().moveBlockToEnd(curBlock_);
return bindUnlabeledBreaks(id);
}
bool branchAndCloseDoWhileLoop(MDefinition* cond, MBasicBlock* loopEntry)
{
size_t id = popLoop();
if (!loopEntry) {
MOZ_ASSERT(inDeadCode());
MOZ_ASSERT(!unlabeledBreaks_.has(id));
return true;
}
MOZ_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1);
if (curBlock_) {
MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1);
if (cond->isConstant()) {
if (cond->toConstant()->valueToBoolean()) {
curBlock_->end(MGoto::New(alloc(), loopEntry));
if (!setLoopBackedge(loopEntry, curBlock_, nullptr))
return false;
curBlock_ = nullptr;
} else {
MBasicBlock* afterLoop;
if (!newBlock(curBlock_, &afterLoop))
return false;
curBlock_->end(MGoto::New(alloc(), afterLoop));
curBlock_ = afterLoop;
}
} else {
MBasicBlock* afterLoop;
if (!newBlock(curBlock_, &afterLoop))
return false;
curBlock_->end(MTest::New(alloc(), cond, loopEntry, afterLoop));
if (!setLoopBackedge(loopEntry, curBlock_, afterLoop))
return false;
curBlock_ = afterLoop;
}
}
return bindUnlabeledBreaks(id);
}
bool bindContinues(size_t id, const LabelVector* maybeLabels)
{
bool createdJoinBlock = false;
if (UnlabeledBlockMap::Ptr p = unlabeledContinues_.lookup(id)) {
if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
return false;
unlabeledContinues_.remove(p);
}
return bindLabeledBreaksOrContinues(maybeLabels, &labeledContinues_, &createdJoinBlock);
}
bool bindLabeledBreaks(const LabelVector* maybeLabels)
{
bool createdJoinBlock = false;
return bindLabeledBreaksOrContinues(maybeLabels, &labeledBreaks_, &createdJoinBlock);
}
bool addBreak(uint32_t* maybeLabelId) {
if (maybeLabelId)
return addBreakOrContinue(*maybeLabelId, &labeledBreaks_);
return addBreakOrContinue(breakableStack_.back(), &unlabeledBreaks_);
}
bool addContinue(uint32_t* maybeLabelId) {
if (maybeLabelId)
return addBreakOrContinue(*maybeLabelId, &labeledContinues_);
return addBreakOrContinue(loopStack_.back(), &unlabeledContinues_);
}
bool startSwitch(size_t id, MDefinition* expr, int32_t low, int32_t high,
MBasicBlock** switchBlock)
{
if (!breakableStack_.append(id))
return false;
if (inDeadCode()) {
*switchBlock = nullptr;
return true;
}
curBlock_->end(MTableSwitch::New(alloc(), expr, low, high));
*switchBlock = curBlock_;
curBlock_ = nullptr;
return true;
}
bool startSwitchCase(MBasicBlock* switchBlock, MBasicBlock** next)
{
if (!switchBlock) {
*next = nullptr;
return true;
}
if (!newBlock(switchBlock, next))
return false;
if (curBlock_) {
curBlock_->end(MGoto::New(alloc(), *next));
if (!(*next)->addPredecessor(alloc(), curBlock_))
return false;
}
curBlock_ = *next;
return true;
}
bool startSwitchDefault(MBasicBlock* switchBlock, BlockVector* cases, MBasicBlock** defaultBlock)
{
if (!startSwitchCase(switchBlock, defaultBlock))
return false;
if (!*defaultBlock)
return true;
mirGraph().moveBlockToEnd(*defaultBlock);
return true;
}
bool joinSwitch(MBasicBlock* switchBlock, const BlockVector& cases, MBasicBlock* defaultBlock)
{
size_t id = breakableStack_.popCopy();
if (!switchBlock)
return true;
MTableSwitch* mir = switchBlock->lastIns()->toTableSwitch();
size_t defaultIndex;
if (!mir->addDefault(defaultBlock, &defaultIndex))
return false;
for (unsigned i = 0; i < cases.length(); i++) {
if (!cases[i]) {
if (!mir->addCase(defaultIndex))
return false;
} else {
size_t caseIndex;
if (!mir->addSuccessor(cases[i], &caseIndex))
return false;
if (!mir->addCase(caseIndex))
return false;
}
}
if (curBlock_) {
MBasicBlock* next;
if (!newBlock(curBlock_, &next))
return false;
curBlock_->end(MGoto::New(alloc(), next));
curBlock_ = next;
}
return bindUnlabeledBreaks(id);
}
// Provides unique identifiers for internal uses in the control flow stacks;
// these ids have to grow monotonically.
unsigned nextId() { return nextId_++; }
/************************************************************ DECODING ***/
uint8_t readU8() { return decoder_.uncheckedReadU8(); }
uint32_t readU32() { return decoder_.uncheckedReadU32(); }
uint32_t readVarU32() { return decoder_.uncheckedReadVarU32(); }
int32_t readI32() { return decoder_.uncheckedReadI32(); }
float readF32() { return decoder_.uncheckedReadF32(); }
double readF64() { return decoder_.uncheckedReadF64(); }
SimdConstant readI32X4() { return decoder_.uncheckedReadI32X4(); }
SimdConstant readF32X4() { return decoder_.uncheckedReadF32X4(); }
Expr readOpcode() { return decoder_.uncheckedReadExpr(); }
Expr peekOpcode() { return decoder_.uncheckedPeekExpr(); }
uint32_t readCallSiteLineOrBytecode() {
if (!func_.callSiteLineNums().empty())
return func_.callSiteLineNums()[lastReadCallSite_++];
return decoder_.offsetOfLastExpr();
}
bool done() const { return decoder_.done(); }
/*************************************************************************/
private:
bool newBlockWithDepth(MBasicBlock* pred, unsigned loopDepth, MBasicBlock** block)
{
*block = MBasicBlock::NewAsmJS(mirGraph(), info(), pred, MBasicBlock::NORMAL);
if (!*block)
return false;
mirGraph().addBlock(*block);
(*block)->setLoopDepth(loopDepth);
return true;
}
bool newBlock(MBasicBlock* pred, MBasicBlock** block)
{
return newBlockWithDepth(pred, loopStack_.length(), block);
}
bool bindBreaksOrContinues(BlockVector* preds, bool* createdJoinBlock)
{
for (unsigned i = 0; i < preds->length(); i++) {
MBasicBlock* pred = (*preds)[i];
if (*createdJoinBlock) {
pred->end(MGoto::New(alloc(), curBlock_));
if (!curBlock_->addPredecessor(alloc(), pred))
return false;
} else {
MBasicBlock* next;
if (!newBlock(pred, &next))
return false;
pred->end(MGoto::New(alloc(), next));
if (curBlock_) {
curBlock_->end(MGoto::New(alloc(), next));
if (!next->addPredecessor(alloc(), curBlock_))
return false;
}
curBlock_ = next;
*createdJoinBlock = true;
}
MOZ_ASSERT(curBlock_->begin() == curBlock_->end());
if (!mirGen_.ensureBallast())
return false;
}
preds->clear();
return true;
}
bool bindLabeledBreaksOrContinues(const LabelVector* maybeLabels, LabeledBlockMap* map,
bool* createdJoinBlock)
{
if (!maybeLabels)
return true;
const LabelVector& labels = *maybeLabels;
for (unsigned i = 0; i < labels.length(); i++) {
if (LabeledBlockMap::Ptr p = map->lookup(labels[i])) {
if (!bindBreaksOrContinues(&p->value(), createdJoinBlock))
return false;
map->remove(p);
}
if (!mirGen_.ensureBallast())
return false;
}
return true;
}
template <class Key, class Map>
bool addBreakOrContinue(Key key, Map* map)
{
if (inDeadCode())
return true;
typename Map::AddPtr p = map->lookupForAdd(key);
if (!p) {
BlockVector empty;
if (!map->add(p, key, Move(empty)))
return false;
}
if (!p->value().append(curBlock_))
return false;
curBlock_ = nullptr;
return true;
}
bool bindUnlabeledBreaks(size_t id)
{
bool createdJoinBlock = false;
if (UnlabeledBlockMap::Ptr p = unlabeledBreaks_.lookup(id)) {
if (!bindBreaksOrContinues(&p->value(), &createdJoinBlock))
return false;
unlabeledBreaks_.remove(p);
}
return true;
}
};
static bool
EmitLiteral(FunctionCompiler& f, ExprType type, MDefinition** def)
{
switch (type) {
case ExprType::I32: {
int32_t val = f.readVarU32();
*def = f.constant(Int32Value(val), MIRType_Int32);
return true;
}
case ExprType::I64: {
MOZ_CRASH("int64");
}
case ExprType::F32: {
float val = f.readF32();
*def = f.constant(Float32Value(val), MIRType_Float32);
return true;
}
case ExprType::F64: {
double val = f.readF64();
*def = f.constant(DoubleValue(val), MIRType_Double);
return true;
}
case ExprType::I32x4: {
SimdConstant lit(f.readI32X4());
*def = f.constant(lit, MIRType_Int32x4);
return true;
}
case ExprType::F32x4: {
SimdConstant lit(f.readF32X4());
*def = f.constant(lit, MIRType_Float32x4);
return true;
}
case ExprType::B32x4: {
// Boolean vectors are stored as an Int vector with -1 / 0 lanes.
SimdConstant lit(f.readI32X4());
*def = f.constant(lit, MIRType_Bool32x4);
return true;
}
case ExprType::Void:
case ExprType::Limit: {
break;
}
}
MOZ_CRASH("unexpected literal type");
}
static bool
EmitGetLocal(FunctionCompiler& f, ExprType type, MDefinition** def)
{
uint32_t slot = f.readVarU32();
*def = f.getLocalDef(slot);
MOZ_ASSERT_IF(type != ExprType::Void, f.localType(slot) == type);
return true;
}
static bool
EmitLoadGlobal(FunctionCompiler& f, ExprType type, MDefinition** def)
{
uint32_t index = f.readVarU32();
const AsmJSGlobalVariable& global = f.mg().globalVar(index);
*def = f.loadGlobalVar(global.globalDataOffset, global.isConst, ToMIRType(global.type));
MOZ_ASSERT_IF(type != ExprType::Void, global.type == type);
return true;
}
static bool EmitExpr(FunctionCompiler&, ExprType, MDefinition**, LabelVector* = nullptr);
static bool EmitExprStmt(FunctionCompiler&, MDefinition**, LabelVector* = nullptr);
static bool
EmitLoadArray(FunctionCompiler& f, Scalar::Type scalarType, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
MDefinition* ptr;
if (!EmitExpr(f, ExprType::I32, &ptr))
return false;
*def = f.loadHeap(scalarType, ptr, needsBoundsCheck);
return true;
}
static bool
EmitStore(FunctionCompiler& f, Scalar::Type viewType, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
MDefinition* ptr;
if (!EmitExpr(f, ExprType::I32, &ptr))
return false;
MDefinition* rhs = nullptr;
switch (viewType) {
case Scalar::Int8:
case Scalar::Int16:
case Scalar::Int32:
if (!EmitExpr(f, ExprType::I32, &rhs))
return false;
break;
case Scalar::Float32:
if (!EmitExpr(f, ExprType::F32, &rhs))
return false;
break;
case Scalar::Float64:
if (!EmitExpr(f, ExprType::F64, &rhs))
return false;
break;
default: MOZ_CRASH("unexpected scalar type");
}
f.storeHeap(viewType, ptr, rhs, needsBoundsCheck);
*def = rhs;
return true;
}
static bool
EmitStoreWithCoercion(FunctionCompiler& f, Scalar::Type rhsType, Scalar::Type viewType,
MDefinition **def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
MDefinition* ptr;
if (!EmitExpr(f, ExprType::I32, &ptr))
return false;
MDefinition* rhs = nullptr;
MDefinition* coerced = nullptr;
if (rhsType == Scalar::Float32 && viewType == Scalar::Float64) {
if (!EmitExpr(f, ExprType::F32, &rhs))
return false;
coerced = f.unary<MToDouble>(rhs);
} else if (rhsType == Scalar::Float64 && viewType == Scalar::Float32) {
if (!EmitExpr(f, ExprType::F64, &rhs))
return false;
coerced = f.unary<MToFloat32>(rhs);
} else {
MOZ_CRASH("unexpected coerced store");
}
f.storeHeap(viewType, ptr, coerced, needsBoundsCheck);
*def = rhs;
return true;
}
static bool
EmitSetLocal(FunctionCompiler& f, ExprType expected, MDefinition** def)
{
uint32_t slot = f.readVarU32();
MDefinition* expr;
ExprType actual = f.localType(slot);
MOZ_ASSERT_IF(expected != ExprType::Void, actual == expected);
if (!EmitExpr(f, actual, &expr))
return false;
f.assign(slot, expr);
*def = expr;
return true;
}
static bool
EmitStoreGlobal(FunctionCompiler& f, ExprType type, MDefinition**def)
{
uint32_t index = f.readVarU32();
const AsmJSGlobalVariable& global = f.mg().globalVar(index);
MOZ_ASSERT_IF(type != ExprType::Void, global.type == type);
MDefinition* expr;
if (!EmitExpr(f, global.type, &expr))
return false;
f.storeGlobalVar(global.globalDataOffset, expr);
*def = expr;
return true;
}
typedef bool IsMax;
static bool
EmitMathMinMax(FunctionCompiler& f, ExprType type, bool isMax, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
MIRType mirType = ToMIRType(type);
*def = f.minMax(lhs, rhs, mirType, isMax);
return true;
}
static bool
EmitAtomicsLoad(FunctionCompiler& f, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
Scalar::Type viewType = Scalar::Type(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
*def = f.atomicLoadHeap(viewType, index, needsBoundsCheck);
return true;
}
static bool
EmitAtomicsStore(FunctionCompiler& f, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
Scalar::Type viewType = Scalar::Type(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
MDefinition* value;
if (!EmitExpr(f, ExprType::I32, &value))
return false;
f.atomicStoreHeap(viewType, index, value, needsBoundsCheck);
*def = value;
return true;
}
static bool
EmitAtomicsBinOp(FunctionCompiler& f, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
Scalar::Type viewType = Scalar::Type(f.readU8());
js::jit::AtomicOp op = js::jit::AtomicOp(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
MDefinition* value;
if (!EmitExpr(f, ExprType::I32, &value))
return false;
*def = f.atomicBinopHeap(op, viewType, index, value, needsBoundsCheck);
return true;
}
static bool
EmitAtomicsCompareExchange(FunctionCompiler& f, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
Scalar::Type viewType = Scalar::Type(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
MDefinition* oldValue;
if (!EmitExpr(f, ExprType::I32, &oldValue))
return false;
MDefinition* newValue;
if (!EmitExpr(f, ExprType::I32, &newValue))
return false;
*def = f.atomicCompareExchangeHeap(viewType, index, oldValue, newValue, needsBoundsCheck);
return true;
}
static bool
EmitAtomicsExchange(FunctionCompiler& f, MDefinition** def)
{
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
Scalar::Type viewType = Scalar::Type(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
MDefinition* value;
if (!EmitExpr(f, ExprType::I32, &value))
return false;
*def = f.atomicExchangeHeap(viewType, index, value, needsBoundsCheck);
return true;
}
static bool
EmitCallArgs(FunctionCompiler& f, const Sig& sig, FunctionCompiler::Call* call)
{
f.startCallArgs(call);
for (ValType argType : sig.args()) {
MDefinition* arg;
if (!EmitExpr(f, ToExprType(argType), &arg))
return false;
if (!f.passArg(arg, argType, call))
return false;
}
f.finishCallArgs(call);
return true;
}
static bool
EmitCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
{
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
uint32_t funcIndex = f.readU32();
const Sig& sig = f.mg().funcSig(funcIndex);
MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret != ExprType::Void, sig.ret() == ret);
FunctionCompiler::Call call(f, lineOrBytecode);
if (!EmitCallArgs(f, sig, &call))
return false;
return f.internalCall(sig, funcIndex, call, def);
}
static bool
EmitFuncPtrCall(FunctionCompiler& f, ExprType ret, MDefinition** def)
{
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
uint32_t mask = f.readU32();
uint32_t globalDataOffset = f.readU32();
uint32_t sigIndex = f.readU32();
const Sig& sig = f.mg().sig(sigIndex);
MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret != ExprType::Void, sig.ret() == ret);
MDefinition *index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
FunctionCompiler::Call call(f, lineOrBytecode);
if (!EmitCallArgs(f, sig, &call))
return false;
return f.funcPtrCall(sig, mask, globalDataOffset, index, call, def);
}
static bool
EmitCallImport(FunctionCompiler& f, ExprType ret, MDefinition** def)
{
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
uint32_t importIndex = f.readU32();
const ModuleImportGeneratorData& import = f.mg().import(importIndex);
const Sig& sig = *import.sig;
MOZ_ASSERT_IF(!IsVoid(sig.ret()) && ret != ExprType::Void, sig.ret() == ret);
FunctionCompiler::Call call(f, lineOrBytecode);
if (!EmitCallArgs(f, sig, &call))
return false;
return f.ffiCall(import.globalDataOffset, call, sig.ret(), def);
}
static bool
EmitF32MathBuiltinCall(FunctionCompiler& f, Expr f32, MDefinition** def)
{
MOZ_ASSERT(f32 == Expr::F32Ceil || f32 == Expr::F32Floor);
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
FunctionCompiler::Call call(f, lineOrBytecode);
f.startCallArgs(&call);
MDefinition* firstArg;
if (!EmitExpr(f, ExprType::F32, &firstArg) || !f.passArg(firstArg, ValType::F32, &call))
return false;
f.finishCallArgs(&call);
SymbolicAddress callee = f32 == Expr::F32Ceil ? SymbolicAddress::CeilF : SymbolicAddress::FloorF;
return f.builtinCall(callee, call, ValType::F32, def);
}
static bool
EmitF64MathBuiltinCall(FunctionCompiler& f, Expr f64, MDefinition** def)
{
uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
FunctionCompiler::Call call(f, lineOrBytecode);
f.startCallArgs(&call);
MDefinition* firstArg;
if (!EmitExpr(f, ExprType::F64, &firstArg) || !f.passArg(firstArg, ValType::F64, &call))
return false;
if (f64 == Expr::F64Pow || f64 == Expr::F64Atan2) {
MDefinition* secondArg;
if (!EmitExpr(f, ExprType::F64, &secondArg) || !f.passArg(secondArg, ValType::F64, &call))
return false;
}
SymbolicAddress callee;
switch (f64) {
case Expr::F64Ceil: callee = SymbolicAddress::CeilD; break;
case Expr::F64Floor: callee = SymbolicAddress::FloorD; break;
case Expr::F64Sin: callee = SymbolicAddress::SinD; break;
case Expr::F64Cos: callee = SymbolicAddress::CosD; break;
case Expr::F64Tan: callee = SymbolicAddress::TanD; break;
case Expr::F64Asin: callee = SymbolicAddress::ASinD; break;
case Expr::F64Acos: callee = SymbolicAddress::ACosD; break;
case Expr::F64Atan: callee = SymbolicAddress::ATanD; break;
case Expr::F64Exp: callee = SymbolicAddress::ExpD; break;
case Expr::F64Log: callee = SymbolicAddress::LogD; break;
case Expr::F64Pow: callee = SymbolicAddress::PowD; break;
case Expr::F64Atan2: callee = SymbolicAddress::ATan2D; break;
default: MOZ_CRASH("unexpected double math builtin callee");
}
f.finishCallArgs(&call);
return f.builtinCall(callee, call, ValType::F64, def);
}
static bool
EmitSimdUnary(FunctionCompiler& f, ExprType type, SimdOperation simdOp, MDefinition** def)
{
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* in;
if (!EmitExpr(f, type, &in))
return false;
*def = f.unarySimd(in, op, ToMIRType(type));
return true;
}
template<class OpKind>
inline bool
EmitSimdBinary(FunctionCompiler& f, ExprType type, OpKind op, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
*def = f.binarySimd(lhs, rhs, op, ToMIRType(type));
return true;
}
static bool
EmitSimdBinaryComp(FunctionCompiler& f, ExprType type, MSimdBinaryComp::Operation op,
MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
*def = f.binarySimd<MSimdBinaryComp>(lhs, rhs, op);
return true;
}
static bool
EmitSimdShift(FunctionCompiler& f, ExprType type, MSimdShift::Operation op, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, ExprType::I32, &rhs))
return false;
*def = f.binarySimd<MSimdShift>(lhs, rhs, op);
return true;
}
static ExprType
SimdToLaneType(ExprType type)
{
switch (type) {
case ExprType::I32x4: return ExprType::I32;
case ExprType::F32x4: return ExprType::F32;
case ExprType::B32x4: return ExprType::I32; // Boolean lanes are Int32 in asm.
case ExprType::I32:
case ExprType::I64:
case ExprType::F32:
case ExprType::F64:
case ExprType::Void:
case ExprType::Limit:;
}
MOZ_CRASH("bad simd type");
}
static bool
EmitExtractLane(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* vec;
if (!EmitExpr(f, type, &vec))
return false;
MDefinition* laneDef;
if (!EmitExpr(f, ExprType::I32, &laneDef))
return false;
if (!laneDef) {
*def = nullptr;
return true;
}
MOZ_ASSERT(laneDef->isConstant());
int32_t laneLit = laneDef->toConstant()->value().toInt32();
MOZ_ASSERT(laneLit < 4);
SimdLane lane = SimdLane(laneLit);
*def = f.extractSimdElement(lane, vec, ToMIRType(SimdToLaneType(type)));
return true;
}
// Emit an I32 expression and then convert it to a boolean SIMD lane value, i.e. -1 or 0.
static bool
EmitSimdBooleanLaneExpr(FunctionCompiler& f, MDefinition** def)
{
MDefinition* i32;
if (!EmitExpr(f, ExprType::I32, &i32))
return false;
// Now compute !i32 - 1 to force the value range into {0, -1}.
MDefinition* noti32 = f.unary<MNot>(i32);
*def = f.binary<MSub>(noti32, f.constant(Int32Value(1), MIRType_Int32), MIRType_Int32);
return true;
}
static bool
EmitSimdReplaceLane(FunctionCompiler& f, ExprType simdType, MDefinition** def)
{
MDefinition* vector;
if (!EmitExpr(f, simdType, &vector))
return false;
MDefinition* laneDef;
if (!EmitExpr(f, ExprType::I32, &laneDef))
return false;
SimdLane lane;
if (laneDef) {
MOZ_ASSERT(laneDef->isConstant());
int32_t laneLit = laneDef->toConstant()->value().toInt32();
MOZ_ASSERT(laneLit < 4);
lane = SimdLane(laneLit);
} else {
lane = SimdLane(-1);
}
MDefinition* scalar;
if (IsSimdBoolType(simdType)) {
if (!EmitSimdBooleanLaneExpr(f, &scalar))
return false;
} else if (!EmitExpr(f, SimdToLaneType(simdType), &scalar)) {
return false;
}
*def = f.insertElementSimd(vector, scalar, lane, ToMIRType(simdType));
return true;
}
template<class T>
inline bool
EmitSimdCast(FunctionCompiler& f, ExprType fromType, ExprType toType, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, fromType, &in))
return false;
*def = f.convertSimd<T>(in, ToMIRType(fromType), ToMIRType(toType));
return true;
}
static bool
EmitSimdSwizzle(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, type, &in))
return false;
uint8_t lanes[4];
for (unsigned i = 0; i < 4; i++)
lanes[i] = f.readU8();
*def = f.swizzleSimd(in, lanes[0], lanes[1], lanes[2], lanes[3], ToMIRType(type));
return true;
}
static bool
EmitSimdShuffle(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
uint8_t lanes[4];
for (unsigned i = 0; i < 4; i++)
lanes[i] = f.readU8();
*def = f.shuffleSimd(lhs, rhs, lanes[0], lanes[1], lanes[2], lanes[3], ToMIRType(type));
return true;
}
static inline Scalar::Type
SimdExprTypeToViewType(ExprType type, unsigned* defaultNumElems)
{
switch (type) {
case ExprType::I32x4: *defaultNumElems = 4; return Scalar::Int32x4;
case ExprType::F32x4: *defaultNumElems = 4; return Scalar::Float32x4;
default: break;
}
MOZ_CRASH("type not handled in SimdExprTypeToViewType");
}
static bool
EmitSimdLoad(FunctionCompiler& f, ExprType type, unsigned numElems, MDefinition** def)
{
unsigned defaultNumElems;
Scalar::Type viewType = SimdExprTypeToViewType(type, &defaultNumElems);
if (!numElems)
numElems = defaultNumElems;
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
*def = f.loadSimdHeap(viewType, index, needsBoundsCheck, numElems);
return true;
}
static bool
EmitSimdStore(FunctionCompiler& f, ExprType type, unsigned numElems, MDefinition** def)
{
unsigned defaultNumElems;
Scalar::Type viewType = SimdExprTypeToViewType(type, &defaultNumElems);
if (!numElems)
numElems = defaultNumElems;
NeedsBoundsCheck needsBoundsCheck = NeedsBoundsCheck(f.readU8());
MDefinition* index;
if (!EmitExpr(f, ExprType::I32, &index))
return false;
MDefinition* vec;
if (!EmitExpr(f, type, &vec))
return false;
f.storeSimdHeap(viewType, index, vec, needsBoundsCheck, numElems);
*def = vec;
return true;
}
static bool
EmitSimdSelect(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* mask;
MDefinition* defs[2];
// The mask is a boolean vector for elementwise select.
if (!EmitExpr(f, ExprType::B32x4, &mask))
return false;
if (!EmitExpr(f, type, &defs[0]) || !EmitExpr(f, type, &defs[1]))
return false;
*def = f.selectSimd(mask, defs[0], defs[1], ToMIRType(type));
return true;
}
static bool
EmitSimdAllTrue(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, type, &in))
return false;
*def = f.simdAllTrue(in);
return true;
}
static bool
EmitSimdAnyTrue(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, type, &in))
return false;
*def = f.simdAnyTrue(in);
return true;
}
static bool
EmitSimdSplat(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* in;
if (IsSimdBoolType(type)) {
if (!EmitSimdBooleanLaneExpr(f, &in))
return false;
} else if (!EmitExpr(f, SimdToLaneType(type), &in)) {
return false;
}
*def = f.splatSimd(in, ToMIRType(type));
return true;
}
static bool
EmitSimdCtor(FunctionCompiler& f, ExprType type, MDefinition** def)
{
switch (type) {
case ExprType::I32x4: {
MDefinition* args[4];
for (unsigned i = 0; i < 4; i++) {
if (!EmitExpr(f, ExprType::I32, &args[i]))
return false;
}
*def = f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3], MIRType_Int32x4);
return true;
}
case ExprType::F32x4: {
MDefinition* args[4];
for (unsigned i = 0; i < 4; i++) {
if (!EmitExpr(f, ExprType::F32, &args[i]))
return false;
}
*def = f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3], MIRType_Float32x4);
return true;
}
case ExprType::B32x4: {
MDefinition* args[4];
for (unsigned i = 0; i < 4; i++) {
if (!EmitSimdBooleanLaneExpr(f, &args[i]))
return false;
}
*def = f.constructSimd<MSimdValueX4>(args[0], args[1], args[2], args[3], MIRType_Bool32x4);
return true;
}
case ExprType::I32:
case ExprType::I64:
case ExprType::F32:
case ExprType::F64:
case ExprType::Void:
case ExprType::Limit:
break;
}
MOZ_CRASH("unexpected SIMD type");
}
template<class T>
static bool
EmitUnary(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, type, &in))
return false;
*def = f.unary<T>(in);
return true;
}
template<class T>
static bool
EmitUnaryMir(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, type, &in))
return false;
*def = f.unary<T>(in, ToMIRType(type));
return true;
}
static bool
EmitMultiply(FunctionCompiler& f, ExprType type, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
MIRType mirType = ToMIRType(type);
*def = f.mul(lhs, rhs, mirType, mirType == MIRType_Int32 ? MMul::Integer : MMul::Normal);
return true;
}
typedef bool IsAdd;
static bool
EmitAddOrSub(FunctionCompiler& f, ExprType type, bool isAdd, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
MIRType mirType = ToMIRType(type);
*def = isAdd ? f.binary<MAdd>(lhs, rhs, mirType) : f.binary<MSub>(lhs, rhs, mirType);
return true;
}
typedef bool IsUnsigned;
typedef bool IsDiv;
static bool
EmitDivOrMod(FunctionCompiler& f, ExprType type, bool isDiv, bool isUnsigned, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, type, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, type, &rhs))
return false;
*def = isDiv
? f.div(lhs, rhs, ToMIRType(type), isUnsigned)
: f.mod(lhs, rhs, ToMIRType(type), isUnsigned);
return true;
}
static bool
EmitDivOrMod(FunctionCompiler& f, ExprType type, bool isDiv, MDefinition** def)
{
MOZ_ASSERT(type != ExprType::I32, "int div or mod must precise signedness");
return EmitDivOrMod(f, type, isDiv, false, def);
}
static bool
EmitComparison(FunctionCompiler& f, Expr expr, MDefinition** def)
{
MDefinition *lhs, *rhs;
MCompare::CompareType compareType;
switch (expr) {
case Expr::I32Eq:
case Expr::I32Ne:
case Expr::I32LeS:
case Expr::I32LtS:
case Expr::I32LeU:
case Expr::I32LtU:
case Expr::I32GeS:
case Expr::I32GtS:
case Expr::I32GeU:
case Expr::I32GtU:
if (!EmitExpr(f, ExprType::I32, &lhs) || !EmitExpr(f, ExprType::I32, &rhs))
return false;
switch (expr) {
case Expr::I32LeS: case Expr::I32LtS: case Expr::I32GeS: case Expr::I32GtS:
case Expr::I32Eq: case Expr::I32Ne:
compareType = MCompare::Compare_Int32; break;
case Expr::I32GeU: case Expr::I32GtU: case Expr::I32LeU: case Expr::I32LtU:
compareType = MCompare::Compare_UInt32; break;
default: MOZ_CRASH("impossibru opcode");
}
break;
case Expr::F32Eq:
case Expr::F32Ne:
case Expr::F32Le:
case Expr::F32Lt:
case Expr::F32Ge:
case Expr::F32Gt:
if (!EmitExpr(f, ExprType::F32, &lhs) || !EmitExpr(f, ExprType::F32, &rhs))
return false;
compareType = MCompare::Compare_Float32;
break;
case Expr::F64Eq:
case Expr::F64Ne:
case Expr::F64Le:
case Expr::F64Lt:
case Expr::F64Ge:
case Expr::F64Gt:
if (!EmitExpr(f, ExprType::F64, &lhs) || !EmitExpr(f, ExprType::F64, &rhs))
return false;
compareType = MCompare::Compare_Double;
break;
default: MOZ_CRASH("unexpected comparison opcode");
}
JSOp compareOp;
switch (expr) {
case Expr::I32Eq:
case Expr::F32Eq:
case Expr::F64Eq:
compareOp = JSOP_EQ;
break;
case Expr::I32Ne:
case Expr::F32Ne:
case Expr::F64Ne:
compareOp = JSOP_NE;
break;
case Expr::I32LeS:
case Expr::I32LeU:
case Expr::F32Le:
case Expr::F64Le:
compareOp = JSOP_LE;
break;
case Expr::I32LtS:
case Expr::I32LtU:
case Expr::F32Lt:
case Expr::F64Lt:
compareOp = JSOP_LT;
break;
case Expr::I32GeS:
case Expr::I32GeU:
case Expr::F32Ge:
case Expr::F64Ge:
compareOp = JSOP_GE;
break;
case Expr::I32GtS:
case Expr::I32GtU:
case Expr::F32Gt:
case Expr::F64Gt:
compareOp = JSOP_GT;
break;
default: MOZ_CRASH("unexpected comparison opcode");
}
*def = f.compare(lhs, rhs, compareOp, compareType);
return true;
}
template<class T>
static bool
EmitBitwise(FunctionCompiler& f, MDefinition** def)
{
MDefinition* lhs;
if (!EmitExpr(f, ExprType::I32, &lhs))
return false;
MDefinition* rhs;
if (!EmitExpr(f, ExprType::I32, &rhs))
return false;
*def = f.bitwise<T>(lhs, rhs);
return true;
}
template<>
bool
EmitBitwise<MBitNot>(FunctionCompiler& f, MDefinition** def)
{
MDefinition* in;
if (!EmitExpr(f, ExprType::I32, &in))
return false;
*def = f.bitwise<MBitNot>(in);
return true;
}
static bool
EmitSimdOp(FunctionCompiler& f, ExprType type, SimdOperation op, MDefinition** def)
{
switch (op) {
case SimdOperation::Constructor:
return EmitSimdCtor(f, type, def);
case SimdOperation::Fn_extractLane:
return EmitExtractLane(f, type, def);
case SimdOperation::Fn_replaceLane:
return EmitSimdReplaceLane(f, type, def);
case SimdOperation::Fn_check:
MOZ_CRASH("only used in asm.js' type system");
case SimdOperation::Fn_splat:
return EmitSimdSplat(f, type, def);
case SimdOperation::Fn_select:
return EmitSimdSelect(f, type, def);
case SimdOperation::Fn_swizzle:
return EmitSimdSwizzle(f, type, def);
case SimdOperation::Fn_shuffle:
return EmitSimdShuffle(f, type, def);
case SimdOperation::Fn_load:
return EmitSimdLoad(f, type, 0, def);
case SimdOperation::Fn_load1:
return EmitSimdLoad(f, type, 1, def);
case SimdOperation::Fn_load2:
return EmitSimdLoad(f, type, 2, def);
case SimdOperation::Fn_load3:
return EmitSimdLoad(f, type, 3, def);
case SimdOperation::Fn_store:
return EmitSimdStore(f, type, 0, def);
case SimdOperation::Fn_store1:
return EmitSimdStore(f, type, 1, def);
case SimdOperation::Fn_store2:
return EmitSimdStore(f, type, 2, def);
case SimdOperation::Fn_store3:
return EmitSimdStore(f, type, 3, def);
case SimdOperation::Fn_allTrue:
return EmitSimdAllTrue(f, type, def);
case SimdOperation::Fn_anyTrue:
return EmitSimdAnyTrue(f, type, def);
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, def);
case SimdOperation::Fn_shiftLeftByScalar:
return EmitSimdShift(f, type, MSimdShift::lsh, def);
case SimdOperation::Fn_shiftRightByScalar:
return EmitSimdShift(f, type,
type == ExprType::I32x4 ? MSimdShift::rsh : MSimdShift::ursh,
def);
case SimdOperation::Fn_shiftRightArithmeticByScalar:
return EmitSimdShift(f, type, MSimdShift::rsh, def);
case SimdOperation::Fn_shiftRightLogicalByScalar:
return EmitSimdShift(f, type, MSimdShift::ursh, def);
#define _CASE(OP) \
case SimdOperation::Fn_##OP: \
return EmitSimdBinaryComp(f, type, MSimdBinaryComp::OP, def);
FOREACH_COMP_SIMD_OP(_CASE)
#undef _CASE
case SimdOperation::Fn_and:
return EmitSimdBinary(f, type, MSimdBinaryBitwise::and_, def);
case SimdOperation::Fn_or:
return EmitSimdBinary(f, type, MSimdBinaryBitwise::or_, def);
case SimdOperation::Fn_xor:
return EmitSimdBinary(f, type, MSimdBinaryBitwise::xor_, def);
#define _CASE(OP) \
case SimdOperation::Fn_##OP: \
return EmitSimdBinary(f, type, MSimdBinaryArith::Op_##OP, def);
FOREACH_NUMERIC_SIMD_BINOP(_CASE)
FOREACH_FLOAT_SIMD_BINOP(_CASE)
#undef _CASE
case SimdOperation::Fn_fromFloat32x4:
return EmitSimdCast<MSimdConvert>(f, ExprType::F32x4, type, def);
case SimdOperation::Fn_fromInt32x4:
return EmitSimdCast<MSimdConvert>(f, ExprType::I32x4, type, def);
case SimdOperation::Fn_fromInt32x4Bits:
return EmitSimdCast<MSimdReinterpretCast>(f, ExprType::I32x4, type, def);
case SimdOperation::Fn_fromFloat32x4Bits:
return EmitSimdCast<MSimdReinterpretCast>(f, ExprType::F32x4, type, def);
case SimdOperation::Fn_fromUint32x4:
case SimdOperation::Fn_fromInt8x16Bits:
case SimdOperation::Fn_fromInt16x8Bits:
case SimdOperation::Fn_fromUint8x16Bits:
case SimdOperation::Fn_fromUint16x8Bits:
case SimdOperation::Fn_fromUint32x4Bits:
case SimdOperation::Fn_fromFloat64x2Bits:
MOZ_CRASH("NYI");
}
MOZ_CRASH("unexpected opcode");
}
static bool
EmitInterruptCheck(FunctionCompiler& f)
{
f.addInterruptCheck();
return true;
}
static bool
EmitInterruptCheckLoop(FunctionCompiler& f)
{
if (!EmitInterruptCheck(f))
return false;
MDefinition* _;
return EmitExprStmt(f, &_);
}
static bool
EmitWhile(FunctionCompiler& f, const LabelVector* maybeLabels)
{
size_t headId = f.nextId();
MBasicBlock* loopEntry;
if (!f.startPendingLoop(headId, &loopEntry))
return false;
MDefinition* condDef;
if (!EmitExpr(f, ExprType::I32, &condDef))
return false;
MBasicBlock* afterLoop;
if (!f.branchAndStartLoopBody(condDef, &afterLoop))
return false;
MDefinition* _;
if (!EmitExprStmt(f, &_))
return false;
if (!f.bindContinues(headId, maybeLabels))
return false;
return f.closeLoop(loopEntry, afterLoop);
}
static bool
EmitFor(FunctionCompiler& f, Expr expr, const LabelVector* maybeLabels)
{
MOZ_ASSERT(expr == Expr::ForInitInc || expr == Expr::ForInitNoInc ||
expr == Expr::ForNoInitInc || expr == Expr::ForNoInitNoInc);
size_t headId = f.nextId();
if (expr == Expr::ForInitInc || expr == Expr::ForInitNoInc) {
MDefinition* _;
if (!EmitExprStmt(f, &_))
return false;
}
MBasicBlock* loopEntry;
if (!f.startPendingLoop(headId, &loopEntry))
return false;
MDefinition* condDef;
if (!EmitExpr(f, ExprType::I32, &condDef))
return false;
MBasicBlock* afterLoop;
if (!f.branchAndStartLoopBody(condDef, &afterLoop))
return false;
MDefinition* _;
if (!EmitExprStmt(f, &_))
return false;
if (!f.bindContinues(headId, maybeLabels))
return false;
if (expr == Expr::ForInitInc || expr == Expr::ForNoInitInc) {
MDefinition* _;
if (!EmitExprStmt(f, &_))
return false;
}
return f.closeLoop(loopEntry, afterLoop);
}
static bool
EmitDoWhile(FunctionCompiler& f, const LabelVector* maybeLabels)
{
size_t headId = f.nextId();
MBasicBlock* loopEntry;
if (!f.startPendingLoop(headId, &loopEntry))
return false;
MDefinition* _;
if (!EmitExprStmt(f, &_))
return false;
if (!f.bindContinues(headId, maybeLabels))
return false;
MDefinition* condDef;
if (!EmitExpr(f, ExprType::I32, &condDef))
return false;
return f.branchAndCloseDoWhileLoop(condDef, loopEntry);
}
static bool
EmitLabel(FunctionCompiler& f, LabelVector* maybeLabels)
{
uint32_t labelId = f.readU32();
if (maybeLabels) {
if (!maybeLabels->append(labelId))
return false;
MDefinition* _;
return EmitExprStmt(f, &_, maybeLabels);
}
LabelVector labels;
if (!labels.append(labelId))
return false;
MDefinition* _;
if (!EmitExprStmt(f, &_, &labels))
return false;
return f.bindLabeledBreaks(&labels);
}
typedef bool HasElseBlock;
static bool
EmitIfElse(FunctionCompiler& f, bool hasElse, ExprType expected, MDefinition** def)
{
// Handle if/else-if chains using iteration instead of recursion. This
// avoids blowing the C stack quota for long if/else-if chains and also
// creates fewer MBasicBlocks at join points (by creating one join block
// for the entire if/else-if chain).
BlockVector thenBlocks;
*def = nullptr;
recurse:
MOZ_ASSERT_IF(!hasElse, expected == ExprType::Void);
MDefinition* condition;
if (!EmitExpr(f, ExprType::I32, &condition))
return false;
MBasicBlock* thenBlock = nullptr;
MBasicBlock* elseOrJoinBlock = nullptr;
if (!f.branchAndStartThen(condition, &thenBlock, &elseOrJoinBlock))
return false;
// From this point, then block.
MDefinition* ifExpr;
if (!EmitExpr(f, expected, &ifExpr))
return false;
if (expected != ExprType::Void)
f.pushPhiInput(ifExpr);
if (!f.appendThenBlock(&thenBlocks))
return false;
if (!hasElse)
return f.joinIf(thenBlocks, elseOrJoinBlock);
f.switchToElse(elseOrJoinBlock);
Expr nextStmt = f.peekOpcode();
if (nextStmt == Expr::If || nextStmt == Expr::IfElse) {
hasElse = nextStmt == Expr::IfElse;
JS_ALWAYS_TRUE(f.readOpcode() == nextStmt);
goto recurse;
}
// From this point, else block.
MDefinition* elseExpr;
if (!EmitExpr(f, expected, &elseExpr))
return false;
if (expected != ExprType::Void)
f.pushPhiInput(elseExpr);
if (!f.joinIfElse(thenBlocks))
return false;
// Now we're on the join block.
if (expected != ExprType::Void)
*def = f.popPhiOutput();
MOZ_ASSERT((expected != ExprType::Void) == !!*def);
return true;
}
static bool
EmitTableSwitch(FunctionCompiler& f)
{
bool hasDefault = f.readU8();
int32_t low = f.readI32();
int32_t high = f.readI32();
uint32_t numCases = f.readU32();
MDefinition* exprDef;
if (!EmitExpr(f, ExprType::I32, &exprDef))
return false;
// Switch with no cases
if (!hasDefault && numCases == 0)
return true;
BlockVector cases;
if (!cases.resize(high - low + 1))
return false;
MBasicBlock* switchBlock;
if (!f.startSwitch(f.nextId(), exprDef, low, high, &switchBlock))
return false;
while (numCases--) {
int32_t caseValue = f.readI32();
MOZ_ASSERT(caseValue >= low && caseValue <= high);
unsigned caseIndex = caseValue - low;
if (!f.startSwitchCase(switchBlock, &cases[caseIndex]))
return false;
MDefinition* _;
if (!EmitExprStmt(f, &_))
return false;
}
MBasicBlock* defaultBlock;
if (!f.startSwitchDefault(switchBlock, &cases, &defaultBlock))
return false;
MDefinition* _;
if (hasDefault && !EmitExprStmt(f, &_))
return false;
return f.joinSwitch(switchBlock, cases, defaultBlock);
}
static bool
EmitRet(FunctionCompiler& f)
{
ExprType ret = f.sig().ret();
if (IsVoid(ret)) {
f.returnVoid();
return true;
}
MDefinition* def;
if (!EmitExpr(f, ret, &def))
return false;
f.returnExpr(def);
return true;
}
static bool
EmitBlock(FunctionCompiler& f, ExprType type, MDefinition** def)
{
uint32_t numStmts = f.readVarU32();
if (numStmts) {
for (uint32_t i = 0; i < numStmts - 1; i++) {
// Fine to clobber def, we only want the last use.
if (!EmitExprStmt(f, def))
return false;
}
if (!EmitExpr(f, type, def))
return false;
}
return true;
}
typedef bool HasLabel;
static bool
EmitContinue(FunctionCompiler& f, bool hasLabel)
{
if (!hasLabel)
return f.addContinue(nullptr);
uint32_t labelId = f.readU32();
return f.addContinue(&labelId);
}
static bool
EmitBreak(FunctionCompiler& f, bool hasLabel)
{
if (!hasLabel)
return f.addBreak(nullptr);
uint32_t labelId = f.readU32();
return f.addBreak(&labelId);
}
static bool
EmitExpr(FunctionCompiler& f, ExprType type, MDefinition** def, LabelVector* maybeLabels)
{
if (!f.mirGen().ensureBallast())
return false;
switch (Expr op = f.readOpcode()) {
case Expr::Nop:
return true;
case Expr::Block:
return EmitBlock(f, type, def);
case Expr::If:
case Expr::IfElse:
return EmitIfElse(f, HasElseBlock(op == Expr::IfElse), type, def);
case Expr::TableSwitch:
return EmitTableSwitch(f);
case Expr::While:
return EmitWhile(f, maybeLabels);
case Expr::DoWhile:
return EmitDoWhile(f, maybeLabels);
case Expr::ForInitInc:
case Expr::ForInitNoInc:
case Expr::ForNoInitNoInc:
case Expr::ForNoInitInc:
return EmitFor(f, op, maybeLabels);
case Expr::Label:
return EmitLabel(f, maybeLabels);
case Expr::Continue:
return EmitContinue(f, HasLabel(false));
case Expr::ContinueLabel:
return EmitContinue(f, HasLabel(true));
case Expr::Break:
return EmitBreak(f, HasLabel(false));
case Expr::BreakLabel:
return EmitBreak(f, HasLabel(true));
case Expr::Return:
return EmitRet(f);
case Expr::Call:
return EmitCall(f, type, def);
case Expr::CallIndirect:
return EmitFuncPtrCall(f, type, def);
case Expr::CallImport:
return EmitCallImport(f, type, def);
case Expr::AtomicsFence:
f.memoryBarrier(MembarFull);
return true;
case Expr::InterruptCheckHead:
return EmitInterruptCheck(f);
case Expr::InterruptCheckLoop:
return EmitInterruptCheckLoop(f);
// Common
case Expr::GetLocal:
return EmitGetLocal(f, type, def);
case Expr::SetLocal:
return EmitSetLocal(f, type, def);
case Expr::LoadGlobal:
return EmitLoadGlobal(f, type, def);
case Expr::StoreGlobal:
return EmitStoreGlobal(f, type, def);
case Expr::Id:
return EmitExpr(f, type, def);
// I32
case Expr::I32Const:
return EmitLiteral(f, ExprType::I32, def);
case Expr::I32Add:
return EmitAddOrSub(f, ExprType::I32, IsAdd(true), def);
case Expr::I32Sub:
return EmitAddOrSub(f, ExprType::I32, IsAdd(false), def);
case Expr::I32Mul:
return EmitMultiply(f, ExprType::I32, def);
case Expr::I32DivS:
case Expr::I32DivU:
return EmitDivOrMod(f, ExprType::I32, IsDiv(true), IsUnsigned(op == Expr::I32DivU), def);
case Expr::I32RemS:
case Expr::I32RemU:
return EmitDivOrMod(f, ExprType::I32, IsDiv(false), IsUnsigned(op == Expr::I32RemU), def);
case Expr::I32Min:
return EmitMathMinMax(f, ExprType::I32, IsMax(false), def);
case Expr::I32Max:
return EmitMathMinMax(f, ExprType::I32, IsMax(true), def);
case Expr::I32Not:
return EmitUnary<MNot>(f, ExprType::I32, def);
case Expr::I32TruncSF32:
case Expr::I32TruncUF32:
return EmitUnary<MTruncateToInt32>(f, ExprType::F32, def);
case Expr::I32TruncSF64:
case Expr::I32TruncUF64:
return EmitUnary<MTruncateToInt32>(f, ExprType::F64, def);
case Expr::I32Clz:
return EmitUnary<MClz>(f, ExprType::I32, def);
case Expr::I32Abs:
return EmitUnaryMir<MAbs>(f, ExprType::I32, def);
case Expr::I32Neg:
return EmitUnaryMir<MAsmJSNeg>(f, ExprType::I32, def);
case Expr::I32Or:
return EmitBitwise<MBitOr>(f, def);
case Expr::I32And:
return EmitBitwise<MBitAnd>(f, def);
case Expr::I32Xor:
return EmitBitwise<MBitXor>(f, def);
case Expr::I32Shl:
return EmitBitwise<MLsh>(f, def);
case Expr::I32ShrS:
return EmitBitwise<MRsh>(f, def);
case Expr::I32ShrU:
return EmitBitwise<MUrsh>(f, def);
case Expr::I32BitNot:
return EmitBitwise<MBitNot>(f, def);
case Expr::I32LoadMem8S:
return EmitLoadArray(f, Scalar::Int8, def);
case Expr::I32LoadMem8U:
return EmitLoadArray(f, Scalar::Uint8, def);
case Expr::I32LoadMem16S:
return EmitLoadArray(f, Scalar::Int16, def);
case Expr::I32LoadMem16U:
return EmitLoadArray(f, Scalar::Uint16, def);
case Expr::I32LoadMem:
return EmitLoadArray(f, Scalar::Int32, def);
case Expr::I32StoreMem8:
return EmitStore(f, Scalar::Int8, def);
case Expr::I32StoreMem16:
return EmitStore(f, Scalar::Int16, def);
case Expr::I32StoreMem:
return EmitStore(f, Scalar::Int32, def);
case Expr::I32Eq:
case Expr::I32Ne:
case Expr::I32LtS:
case Expr::I32LeS:
case Expr::I32GtS:
case Expr::I32GeS:
case Expr::I32LtU:
case Expr::I32LeU:
case Expr::I32GtU:
case Expr::I32GeU:
case Expr::F32Eq:
case Expr::F32Ne:
case Expr::F32Lt:
case Expr::F32Le:
case Expr::F32Gt:
case Expr::F32Ge:
case Expr::F64Eq:
case Expr::F64Ne:
case Expr::F64Lt:
case Expr::F64Le:
case Expr::F64Gt:
case Expr::F64Ge:
return EmitComparison(f, op, def);
case Expr::I32AtomicsCompareExchange:
return EmitAtomicsCompareExchange(f, def);
case Expr::I32AtomicsExchange:
return EmitAtomicsExchange(f, def);
case Expr::I32AtomicsLoad:
return EmitAtomicsLoad(f, def);
case Expr::I32AtomicsStore:
return EmitAtomicsStore(f, def);
case Expr::I32AtomicsBinOp:
return EmitAtomicsBinOp(f, def);
// F32
case Expr::F32Const:
return EmitLiteral(f, ExprType::F32, def);
case Expr::F32Add:
return EmitAddOrSub(f, ExprType::F32, IsAdd(true), def);
case Expr::F32Sub:
return EmitAddOrSub(f, ExprType::F32, IsAdd(false), def);
case Expr::F32Mul:
return EmitMultiply(f, ExprType::F32, def);
case Expr::F32Div:
return EmitDivOrMod(f, ExprType::F32, IsDiv(true), def);
case Expr::F32Min:
return EmitMathMinMax(f, ExprType::F32, IsMax(false), def);
case Expr::F32Max:
return EmitMathMinMax(f, ExprType::F32, IsMax(true), def);
case Expr::F32Neg:
return EmitUnaryMir<MAsmJSNeg>(f, ExprType::F32, def);
case Expr::F32Abs:
return EmitUnaryMir<MAbs>(f, ExprType::F32, def);
case Expr::F32Sqrt:
return EmitUnaryMir<MSqrt>(f, ExprType::F32, def);
case Expr::F32Ceil:
case Expr::F32Floor:
return EmitF32MathBuiltinCall(f, op, def);
case Expr::F32DemoteF64:
return EmitUnary<MToFloat32>(f, ExprType::F64, def);
case Expr::F32ConvertSI32:
return EmitUnary<MToFloat32>(f, ExprType::I32, def);
case Expr::F32ConvertUI32:
return EmitUnary<MAsmJSUnsignedToFloat32>(f, ExprType::I32, def);
case Expr::F32LoadMem:
return EmitLoadArray(f, Scalar::Float32, def);
case Expr::F32StoreMem:
return EmitStore(f, Scalar::Float32, def);
case Expr::F32StoreMemF64:
return EmitStoreWithCoercion(f, Scalar::Float32, Scalar::Float64, def);
// F64
case Expr::F64Const:
return EmitLiteral(f, ExprType::F64, def);
case Expr::F64Add:
return EmitAddOrSub(f, ExprType::F64, IsAdd(true), def);
case Expr::F64Sub:
return EmitAddOrSub(f, ExprType::F64, IsAdd(false), def);
case Expr::F64Mul:
return EmitMultiply(f, ExprType::F64, def);
case Expr::F64Div:
return EmitDivOrMod(f, ExprType::F64, IsDiv(true), def);
case Expr::F64Mod:
return EmitDivOrMod(f, ExprType::F64, IsDiv(false), def);
case Expr::F64Min:
return EmitMathMinMax(f, ExprType::F64, IsMax(false), def);
case Expr::F64Max:
return EmitMathMinMax(f, ExprType::F64, IsMax(true), def);
case Expr::F64Neg:
return EmitUnaryMir<MAsmJSNeg>(f, ExprType::F64, def);
case Expr::F64Abs:
return EmitUnaryMir<MAbs>(f, ExprType::F64, def);
case Expr::F64Sqrt:
return EmitUnaryMir<MSqrt>(f, ExprType::F64, def);
case Expr::F64Ceil:
case Expr::F64Floor:
case Expr::F64Sin:
case Expr::F64Cos:
case Expr::F64Tan:
case Expr::F64Asin:
case Expr::F64Acos:
case Expr::F64Atan:
case Expr::F64Exp:
case Expr::F64Log:
case Expr::F64Pow:
case Expr::F64Atan2:
return EmitF64MathBuiltinCall(f, op, def);
case Expr::F64PromoteF32:
return EmitUnary<MToDouble>(f, ExprType::F32, def);
case Expr::F64ConvertSI32:
return EmitUnary<MToDouble>(f, ExprType::I32, def);
case Expr::F64ConvertUI32:
return EmitUnary<MAsmJSUnsignedToDouble>(f, ExprType::I32, def);
case Expr::F64LoadMem:
return EmitLoadArray(f, Scalar::Float64, def);
case Expr::F64StoreMem:
return EmitStore(f, Scalar::Float64, def);
case Expr::F64StoreMemF32:
return EmitStoreWithCoercion(f, Scalar::Float64, Scalar::Float32, def);
// SIMD
#define CASE(TYPE, OP) \
case Expr::TYPE##OP: \
return EmitSimdOp(f, ExprType::TYPE, SimdOperation::Fn_##OP, def);
#define I32CASE(OP) CASE(I32x4, OP)
#define F32CASE(OP) CASE(F32x4, OP)
#define B32CASE(OP) CASE(B32x4, OP)
#define ENUMERATE(TYPE, FORALL, DO) \
case Expr::TYPE##Const: \
return EmitLiteral(f, ExprType::TYPE, def); \
case Expr::TYPE##Constructor: \
return EmitSimdOp(f, ExprType::TYPE, SimdOperation::Constructor, def); \
FORALL(DO)
ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32CASE)
ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32CASE)
ENUMERATE(B32x4, FORALL_BOOL_SIMD_OP, B32CASE)
#undef CASE
#undef I32CASE
#undef F32CASE
#undef B32CASE
#undef ENUMERATE
// Future opcodes
case Expr::Loop:
case Expr::Select:
case Expr::Br:
case Expr::BrIf:
case Expr::I8Const:
case Expr::I32Ctz:
case Expr::I32Popcnt:
case Expr::F32CopySign:
case Expr::F32Trunc:
case Expr::F32Nearest:
case Expr::F64CopySign:
case Expr::F64Nearest:
case Expr::F64Trunc:
case Expr::I32WrapI64:
case Expr::I64Const:
case Expr::I64ExtendSI32:
case Expr::I64ExtendUI32:
case Expr::I64TruncSF32:
case Expr::I64TruncSF64:
case Expr::I64TruncUF32:
case Expr::I64TruncUF64:
case Expr::F32ConvertSI64:
case Expr::F32ConvertUI64:
case Expr::F64ConvertSI64:
case Expr::F64ConvertUI64:
case Expr::I64ReinterpretF64:
case Expr::F64ReinterpretI64:
case Expr::I32ReinterpretF32:
case Expr::F32ReinterpretI32:
case Expr::I64LoadMem8S:
case Expr::I64LoadMem16S:
case Expr::I64LoadMem32S:
case Expr::I64LoadMem8U:
case Expr::I64LoadMem16U:
case Expr::I64LoadMem32U:
case Expr::I64LoadMem:
case Expr::I64StoreMem8:
case Expr::I64StoreMem16:
case Expr::I64StoreMem32:
case Expr::I64StoreMem:
case Expr::I64Clz:
case Expr::I64Ctz:
case Expr::I64Popcnt:
case Expr::I64Add:
case Expr::I64Sub:
case Expr::I64Mul:
case Expr::I64DivS:
case Expr::I64DivU:
case Expr::I64RemS:
case Expr::I64RemU:
case Expr::I64Or:
case Expr::I64And:
case Expr::I64Xor:
case Expr::I64Shl:
case Expr::I64ShrU:
case Expr::I64ShrS:
case Expr::I64Eq:
case Expr::I64Ne:
case Expr::I64LtS:
case Expr::I64LeS:
case Expr::I64LtU:
case Expr::I64LeU:
case Expr::I64GtS:
case Expr::I64GeS:
case Expr::I64GtU:
case Expr::I64GeU:
MOZ_CRASH("NYI");
case Expr::Unreachable:
break;
case Expr::Limit:
MOZ_CRASH("Limit");
}
MOZ_CRASH("unexpected wasm opcode");
}
static bool
EmitExprStmt(FunctionCompiler& f, MDefinition** def, LabelVector* maybeLabels)
{
return EmitExpr(f, ExprType::Void, def, maybeLabels);
}
bool
wasm::IonCompileFunction(IonCompileTask* task)
{
int64_t before = PRMJ_Now();
const FuncBytecode& func = task->func();
FuncCompileResults& results = task->results();
JitContext jitContext(CompileRuntime::get(task->runtime()), &results.alloc());
const JitCompileOptions options;
MIRGraph graph(&results.alloc());
CompileInfo compileInfo(func.numLocals());
MIRGenerator mir(nullptr, options, &results.alloc(), &graph, &compileInfo,
IonOptimizations.get(OptimizationLevel::AsmJS),
task->args().useSignalHandlersForOOB);
// Build MIR graph
{
FunctionCompiler f(task->mg(), func, mir, results);
if (!f.init())
return false;
MDefinition* last = nullptr;
if (func.isAsmJS()) {
while (!f.done()) {
if (!EmitExprStmt(f, &last))
return false;
}
} else {
if (!EmitExpr(f, f.sig().ret(), &last))
return false;
MOZ_ASSERT(f.done());
}
MOZ_ASSERT(IsVoid(f.sig().ret()) || f.inDeadCode() || last);
if (IsVoid(f.sig().ret()))
f.returnVoid();
else
f.returnExpr(last);
f.checkPostconditions();
}
// Compile MIR graph
{
jit::SpewBeginFunction(&mir, nullptr);
jit::AutoSpewEndFunction spewEndFunction(&mir);
if (!OptimizeMIR(&mir))
return false;
LIRGraph* lir = GenerateLIR(&mir);
if (!lir)
return false;
CodeGenerator codegen(&mir, lir, &results.masm());
if (!codegen.generateAsmJS(&results.offsets()))
return false;
}
results.setCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
return true;
}