diff -r 000000000000 -r 6474c204b198 js/src/jit/AsmJS.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js/src/jit/AsmJS.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,7128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/AsmJS.h" + +#include "mozilla/Move.h" + +#ifdef MOZ_VTUNE +# include "vtune/VTuneWrapper.h" +#endif + +#include "jsmath.h" +#include "jsprf.h" +#include "jsworkers.h" +#include "prmjtime.h" + +#include "assembler/assembler/MacroAssembler.h" +#include "frontend/Parser.h" +#include "jit/AsmJSLink.h" +#include "jit/AsmJSModule.h" +#include "jit/AsmJSSignalHandlers.h" +#include "jit/CodeGenerator.h" +#include "jit/CompileWrappers.h" +#include "jit/MIR.h" +#include "jit/MIRGraph.h" +#ifdef JS_ION_PERF +# include "jit/PerfSpewer.h" +#endif +#include "vm/Interpreter.h" + +#include "jsinferinlines.h" +#include "jsobjinlines.h" + +#include "frontend/ParseNode-inl.h" +#include "frontend/Parser-inl.h" + +using namespace js; +using namespace js::frontend; +using namespace js::jit; + +using mozilla::AddToHash; +using mozilla::ArrayLength; +using mozilla::CountLeadingZeroes32; +using mozilla::DebugOnly; +using mozilla::HashGeneric; +using mozilla::IsNaN; +using mozilla::IsNegativeZero; +using mozilla::Maybe; +using mozilla::Move; +using mozilla::PositiveInfinity; +using JS::GenericNaN; + +static const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12; + +/*****************************************************************************/ +// ParseNode utilities + +static inline ParseNode * +NextNode(ParseNode *pn) +{ + return pn->pn_next; +} + +static inline ParseNode * +UnaryKid(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_UNARY)); + return pn->pn_kid; +} + +static inline ParseNode * +ReturnExpr(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_RETURN)); + return UnaryKid(pn); +} + +static inline ParseNode * +BinaryRight(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_BINARY)); + return pn->pn_right; +} + +static inline ParseNode * +BinaryLeft(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_BINARY)); + return pn->pn_left; +} + +static inline ParseNode * +TernaryKid1(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_TERNARY)); + return pn->pn_kid1; +} + +static inline ParseNode * +TernaryKid2(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_TERNARY)); + return pn->pn_kid2; +} + +static inline ParseNode * +TernaryKid3(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_TERNARY)); + return pn->pn_kid3; +} + +static inline ParseNode * +ListHead(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_LIST)); + return pn->pn_head; +} + +static inline unsigned +ListLength(ParseNode *pn) +{ + JS_ASSERT(pn->isArity(PN_LIST)); + return pn->pn_count; +} + +static inline ParseNode * +CallCallee(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_CALL)); + return ListHead(pn); +} + +static inline unsigned +CallArgListLength(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_CALL)); + JS_ASSERT(ListLength(pn) >= 1); + return ListLength(pn) - 1; +} + +static inline ParseNode * +CallArgList(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_CALL)); + return NextNode(ListHead(pn)); +} + +static inline ParseNode * +VarListHead(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)); + return ListHead(pn); +} + +static inline ParseNode * +CaseExpr(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_CASE) || pn->isKind(PNK_DEFAULT)); + return BinaryLeft(pn); +} + +static inline ParseNode * +CaseBody(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_CASE) || pn->isKind(PNK_DEFAULT)); + return BinaryRight(pn); +} + +static inline bool +IsExpressionStatement(ParseNode *pn) +{ + return pn->isKind(PNK_SEMI); +} + +static inline ParseNode * +ExpressionStatementExpr(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_SEMI)); + return UnaryKid(pn); +} + +static inline PropertyName * +LoopControlMaybeLabel(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_BREAK) || pn->isKind(PNK_CONTINUE)); + JS_ASSERT(pn->isArity(PN_NULLARY)); + return pn->as().label(); +} + +static inline PropertyName * +LabeledStatementLabel(ParseNode *pn) +{ + return pn->as().label(); +} + +static inline ParseNode * +LabeledStatementStatement(ParseNode *pn) +{ + return pn->as().statement(); +} + +static double +NumberNodeValue(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_NUMBER)); + return pn->pn_dval; +} + +static bool +NumberNodeHasFrac(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_NUMBER)); + return pn->pn_u.number.decimalPoint == HasDecimal; +} + +static ParseNode * +DotBase(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_DOT)); + JS_ASSERT(pn->isArity(PN_NAME)); + return pn->expr(); +} + +static PropertyName * +DotMember(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_DOT)); + JS_ASSERT(pn->isArity(PN_NAME)); + return pn->pn_atom->asPropertyName(); +} + +static ParseNode * +ElemBase(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_ELEM)); + return BinaryLeft(pn); +} + +static ParseNode * +ElemIndex(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_ELEM)); + return BinaryRight(pn); +} + +static inline JSFunction * +FunctionObject(ParseNode *fn) +{ + JS_ASSERT(fn->isKind(PNK_FUNCTION)); + JS_ASSERT(fn->isArity(PN_CODE)); + return fn->pn_funbox->function(); +} + +static inline PropertyName * +FunctionName(ParseNode *fn) +{ + if (JSAtom *atom = FunctionObject(fn)->atom()) + return atom->asPropertyName(); + return nullptr; +} + +static inline ParseNode * +FunctionStatementList(ParseNode *fn) +{ + JS_ASSERT(fn->pn_body->isKind(PNK_ARGSBODY)); + ParseNode *last = fn->pn_body->last(); + JS_ASSERT(last->isKind(PNK_STATEMENTLIST)); + return last; +} + +static inline bool +IsNormalObjectField(ExclusiveContext *cx, ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_COLON)); + return pn->getOp() == JSOP_INITPROP && + BinaryLeft(pn)->isKind(PNK_NAME) && + BinaryLeft(pn)->name() != cx->names().proto; +} + +static inline PropertyName * +ObjectNormalFieldName(ExclusiveContext *cx, ParseNode *pn) +{ + JS_ASSERT(IsNormalObjectField(cx, pn)); + return BinaryLeft(pn)->name(); +} + +static inline ParseNode * +ObjectFieldInitializer(ParseNode *pn) +{ + JS_ASSERT(pn->isKind(PNK_COLON)); + return BinaryRight(pn); +} + +static inline bool +IsDefinition(ParseNode *pn) +{ + return pn->isKind(PNK_NAME) && pn->isDefn(); +} + +static inline ParseNode * +MaybeDefinitionInitializer(ParseNode *pn) +{ + JS_ASSERT(IsDefinition(pn)); + return pn->expr(); +} + +static inline bool +IsUseOfName(ParseNode *pn, PropertyName *name) +{ + return pn->isKind(PNK_NAME) && pn->name() == name; +} + +static inline bool +IsEmptyStatement(ParseNode *pn) +{ + return pn->isKind(PNK_SEMI) && !UnaryKid(pn); +} + +static inline ParseNode * +SkipEmptyStatements(ParseNode *pn) +{ + while (pn && IsEmptyStatement(pn)) + pn = pn->pn_next; + return pn; +} + +static inline ParseNode * +NextNonEmptyStatement(ParseNode *pn) +{ + return SkipEmptyStatements(pn->pn_next); +} + +static TokenKind +PeekToken(AsmJSParser &parser) +{ + TokenStream &ts = parser.tokenStream; + while (ts.peekToken(TokenStream::Operand) == TOK_SEMI) + ts.consumeKnownToken(TOK_SEMI); + return ts.peekToken(TokenStream::Operand); +} + +static bool +ParseVarOrConstStatement(AsmJSParser &parser, ParseNode **var) +{ + TokenKind tk = PeekToken(parser); + if (tk != TOK_VAR && tk != TOK_CONST) { + *var = nullptr; + return true; + } + + *var = parser.statement(); + if (!*var) + return false; + + JS_ASSERT((*var)->isKind(PNK_VAR) || (*var)->isKind(PNK_CONST)); + return true; +} + +/*****************************************************************************/ + +namespace { + +// Respresents the type of a general asm.js expression. +class Type +{ + public: + enum Which { + Double, + MaybeDouble, + Float, + MaybeFloat, + Floatish, + Fixnum, + Int, + Signed, + Unsigned, + Intish, + Void + }; + + private: + Which which_; + + public: + Type() : which_(Which(-1)) {} + Type(Which w) : which_(w) {} + + bool operator==(Type rhs) const { return which_ == rhs.which_; } + bool operator!=(Type rhs) const { return which_ != rhs.which_; } + + bool isSigned() const { + return which_ == Signed || which_ == Fixnum; + } + + bool isUnsigned() const { + return which_ == Unsigned || which_ == Fixnum; + } + + bool isInt() const { + return isSigned() || isUnsigned() || which_ == Int; + } + + bool isIntish() const { + return isInt() || which_ == Intish; + } + + bool isDouble() const { + return which_ == Double; + } + + bool isMaybeDouble() const { + return isDouble() || which_ == MaybeDouble; + } + + bool isFloat() const { + return which_ == Float; + } + + bool isMaybeFloat() const { + return isFloat() || which_ == MaybeFloat; + } + + bool isFloatish() const { + return isMaybeFloat() || which_ == Floatish; + } + + bool isVoid() const { + return which_ == Void; + } + + bool isExtern() const { + return isDouble() || isSigned(); + } + + bool isVarType() const { + return isInt() || isDouble() || isFloat(); + } + + MIRType toMIRType() const { + switch (which_) { + case Double: + case MaybeDouble: + return MIRType_Double; + case Float: + case Floatish: + case MaybeFloat: + return MIRType_Float32; + case Fixnum: + case Int: + case Signed: + case Unsigned: + case Intish: + return MIRType_Int32; + case Void: + return MIRType_None; + } + MOZ_ASSUME_UNREACHABLE("Invalid Type"); + } + + const char *toChars() const { + switch (which_) { + case Double: return "double"; + case MaybeDouble: return "double?"; + case Float: return "float"; + case Floatish: return "floatish"; + case MaybeFloat: return "float?"; + case Fixnum: return "fixnum"; + case Int: return "int"; + case Signed: return "signed"; + case Unsigned: return "unsigned"; + case Intish: return "intish"; + case Void: return "void"; + } + MOZ_ASSUME_UNREACHABLE("Invalid Type"); + } +}; + +} /* anonymous namespace */ + +// Represents the subset of Type that can be used as the return type of a +// function. +class RetType +{ + public: + enum Which { + Void = Type::Void, + Signed = Type::Signed, + Double = Type::Double, + Float = Type::Float + }; + + private: + Which which_; + + public: + RetType() : which_(Which(-1)) {} + RetType(Which w) : which_(w) {} + RetType(AsmJSCoercion coercion) { + switch (coercion) { + case AsmJS_ToInt32: which_ = Signed; break; + case AsmJS_ToNumber: which_ = Double; break; + case AsmJS_FRound: which_ = Float; break; + } + } + Which which() const { + return which_; + } + Type toType() const { + return Type::Which(which_); + } + AsmJSModule::ReturnType toModuleReturnType() const { + switch (which_) { + case Void: return AsmJSModule::Return_Void; + case Signed: return AsmJSModule::Return_Int32; + case Float: // will be converted to a Double + case Double: return AsmJSModule::Return_Double; + } + MOZ_ASSUME_UNREACHABLE("Unexpected return type"); + } + MIRType toMIRType() const { + switch (which_) { + case Void: return MIRType_None; + case Signed: return MIRType_Int32; + case Double: return MIRType_Double; + case Float: return MIRType_Float32; + } + MOZ_ASSUME_UNREACHABLE("Unexpected return type"); + } + bool operator==(RetType rhs) const { return which_ == rhs.which_; } + bool operator!=(RetType rhs) const { return which_ != rhs.which_; } +}; + +namespace { + +// Represents the subset of Type that can be used as a variable or +// argument's type. Note: AsmJSCoercion and VarType are kept separate to +// make very clear the signed/int distinction: a coercion may explicitly sign +// an *expression* but, when stored as a variable, this signedness information +// is explicitly thrown away by the asm.js type system. E.g., in +// +// function f(i) { +// i = i | 0; (1) +// if (...) +// i = foo() >>> 0; +// else +// i = bar() | 0; +// return i | 0; (2) +// } +// +// the AsmJSCoercion of (1) is Signed (since | performs ToInt32) but, when +// translated to an VarType, the result is a plain Int since, as shown, it +// is legal to assign both Signed and Unsigned (or some other Int) values to +// it. For (2), the AsmJSCoercion is also Signed but, when translated to an +// RetType, the result is Signed since callers (asm.js and non-asm.js) can +// rely on the return value being Signed. +class VarType +{ + public: + enum Which { + Int = Type::Int, + Double = Type::Double, + Float = Type::Float + }; + + private: + Which which_; + + public: + VarType() + : which_(Which(-1)) {} + VarType(Which w) + : which_(w) {} + VarType(AsmJSCoercion coercion) { + switch (coercion) { + case AsmJS_ToInt32: which_ = Int; break; + case AsmJS_ToNumber: which_ = Double; break; + case AsmJS_FRound: which_ = Float; break; + } + } + Which which() const { + return which_; + } + Type toType() const { + return Type::Which(which_); + } + MIRType toMIRType() const { + switch(which_) { + case Int: return MIRType_Int32; + case Double: return MIRType_Double; + case Float: return MIRType_Float32; + } + MOZ_ASSUME_UNREACHABLE("VarType can only be Int, Double or Float"); + } + AsmJSCoercion toCoercion() const { + switch(which_) { + case Int: return AsmJS_ToInt32; + case Double: return AsmJS_ToNumber; + case Float: return AsmJS_FRound; + } + MOZ_ASSUME_UNREACHABLE("VarType can only be Int, Double or Float"); + } + static VarType FromCheckedType(Type type) { + JS_ASSERT(type.isInt() || type.isMaybeDouble() || type.isFloatish()); + if (type.isMaybeDouble()) + return Double; + else if (type.isFloatish()) + return Float; + else + return Int; + } + bool operator==(VarType rhs) const { return which_ == rhs.which_; } + bool operator!=(VarType rhs) const { return which_ != rhs.which_; } +}; + +} /* anonymous namespace */ + +// Implements <: (subtype) operator when the rhs is an VarType +static inline bool +operator<=(Type lhs, VarType rhs) +{ + switch (rhs.which()) { + case VarType::Int: return lhs.isInt(); + case VarType::Double: return lhs.isDouble(); + case VarType::Float: return lhs.isFloat(); + } + MOZ_ASSUME_UNREACHABLE("Unexpected rhs type"); +} + +/*****************************************************************************/ + +static inline MIRType ToMIRType(MIRType t) { return t; } +static inline MIRType ToMIRType(VarType t) { return t.toMIRType(); } + +namespace { + +template +class ABIArgIter +{ + ABIArgGenerator gen_; + const VecT &types_; + unsigned i_; + + void settle() { if (!done()) gen_.next(ToMIRType(types_[i_])); } + + public: + ABIArgIter(const VecT &types) : types_(types), i_(0) { settle(); } + void operator++(int) { JS_ASSERT(!done()); i_++; settle(); } + bool done() const { return i_ == types_.length(); } + + ABIArg *operator->() { JS_ASSERT(!done()); return &gen_.current(); } + ABIArg &operator*() { JS_ASSERT(!done()); return gen_.current(); } + + unsigned index() const { JS_ASSERT(!done()); return i_; } + MIRType mirType() const { JS_ASSERT(!done()); return ToMIRType(types_[i_]); } + uint32_t stackBytesConsumedSoFar() const { return gen_.stackBytesConsumedSoFar(); } +}; + +typedef js::Vector MIRTypeVector; +typedef ABIArgIter ABIArgMIRTypeIter; + +typedef js::Vector VarTypeVector; +typedef ABIArgIter ABIArgTypeIter; + +class Signature +{ + VarTypeVector argTypes_; + RetType retType_; + + public: + Signature(LifoAlloc &alloc) + : argTypes_(alloc) {} + Signature(LifoAlloc &alloc, RetType retType) + : argTypes_(alloc), retType_(retType) {} + Signature(VarTypeVector &&argTypes, RetType retType) + : argTypes_(Move(argTypes)), retType_(Move(retType)) {} + Signature(Signature &&rhs) + : argTypes_(Move(rhs.argTypes_)), retType_(Move(rhs.retType_)) {} + + bool copy(const Signature &rhs) { + if (!argTypes_.resize(rhs.argTypes_.length())) + return false; + for (unsigned i = 0; i < argTypes_.length(); i++) + argTypes_[i] = rhs.argTypes_[i]; + retType_ = rhs.retType_; + return true; + } + + bool appendArg(VarType type) { return argTypes_.append(type); } + VarType arg(unsigned i) const { return argTypes_[i]; } + const VarTypeVector &args() const { return argTypes_; } + VarTypeVector &&extractArgs() { return Move(argTypes_); } + + RetType retType() const { return retType_; } +}; + +} /* namespace anonymous */ + +static +bool operator==(const Signature &lhs, const Signature &rhs) +{ + if (lhs.retType() != rhs.retType()) + return false; + if (lhs.args().length() != rhs.args().length()) + return false; + for (unsigned i = 0; i < lhs.args().length(); i++) { + if (lhs.arg(i) != rhs.arg(i)) + return false; + } + return true; +} + +static inline +bool operator!=(const Signature &lhs, const Signature &rhs) +{ + return !(lhs == rhs); +} + +/*****************************************************************************/ +// Typed array utilities + +static Type +TypedArrayLoadType(ArrayBufferView::ViewType viewType) +{ + switch (viewType) { + case ArrayBufferView::TYPE_INT8: + case ArrayBufferView::TYPE_INT16: + case ArrayBufferView::TYPE_INT32: + case ArrayBufferView::TYPE_UINT8: + case ArrayBufferView::TYPE_UINT16: + case ArrayBufferView::TYPE_UINT32: + return Type::Intish; + case ArrayBufferView::TYPE_FLOAT32: + return Type::MaybeFloat; + case ArrayBufferView::TYPE_FLOAT64: + return Type::MaybeDouble; + default:; + } + MOZ_ASSUME_UNREACHABLE("Unexpected array type"); +} + +enum NeedsBoundsCheck { + NO_BOUNDS_CHECK, + NEEDS_BOUNDS_CHECK +}; + +namespace { + +typedef js::Vector LabelVector; +typedef js::Vector BlockVector; + +// ModuleCompiler encapsulates the compilation of an entire asm.js module. Over +// the course of an ModuleCompiler object's lifetime, many FunctionCompiler +// objects will be created and destroyed in sequence, one for each function in +// the module. +// +// *** asm.js FFI calls *** +// +// asm.js allows calling out to non-asm.js via "FFI calls". The asm.js type +// system does not place any constraints on the FFI call. In particular: +// - an FFI call's target is not known or speculated at module-compile time; +// - a single external function can be called with different signatures. +// +// If performance didn't matter, all FFI calls could simply box their arguments +// and call js::Invoke. However, we'd like to be able to specialize FFI calls +// to be more efficient in several cases: +// +// - for calls to JS functions which have been jitted, we'd like to call +// directly into JIT code without going through C++. +// +// - for calls to certain builtins, we'd like to be call directly into the C++ +// code for the builtin without going through the general call path. +// +// All of this requires dynamic specialization techniques which must happen +// after module compilation. To support this, at module-compilation time, each +// FFI call generates a call signature according to the system ABI, as if the +// callee was a C++ function taking/returning the same types as the caller was +// passing/expecting. The callee is loaded from a fixed offset in the global +// data array which allows the callee to change at runtime. Initially, the +// callee is stub which boxes its arguments and calls js::Invoke. +// +// To do this, we need to generate a callee stub for each pairing of FFI callee +// and signature. We call this pairing an "exit". For example, this code has +// two external functions and three exits: +// +// function f(global, imports) { +// "use asm"; +// var foo = imports.foo; +// var bar = imports.bar; +// function g() { +// foo(1); // Exit #1: (int) -> void +// foo(1.5); // Exit #2: (double) -> void +// bar(1)|0; // Exit #3: (int) -> int +// bar(2)|0; // Exit #3: (int) -> int +// } +// } +// +// The ModuleCompiler maintains a hash table (ExitMap) which allows a call site +// to add a new exit or reuse an existing one. The key is an ExitDescriptor +// (which holds the exit pairing) and the value is an index into the +// Vector stored in the AsmJSModule. +// +// Rooting note: ModuleCompiler is a stack class that contains unrooted +// PropertyName (JSAtom) pointers. This is safe because it cannot be +// constructed without a TokenStream reference. TokenStream is itself a stack +// class that cannot be constructed without an AutoKeepAtoms being live on the +// stack, which prevents collection of atoms. +// +// ModuleCompiler is marked as rooted in the rooting analysis. Don't add +// non-JSAtom pointers, or this will break! +class MOZ_STACK_CLASS ModuleCompiler +{ + public: + class Func + { + PropertyName *name_; + bool defined_; + uint32_t srcOffset_; + uint32_t endOffset_; + Signature sig_; + Label *code_; + unsigned compileTime_; + + public: + Func(PropertyName *name, Signature &&sig, Label *code) + : name_(name), defined_(false), srcOffset_(0), endOffset_(0), sig_(Move(sig)), + code_(code), compileTime_(0) + {} + + PropertyName *name() const { return name_; } + + bool defined() const { return defined_; } + void finish(uint32_t start, uint32_t end) { + JS_ASSERT(!defined_); + defined_ = true; + srcOffset_ = start; + endOffset_ = end; + } + + uint32_t srcOffset() const { JS_ASSERT(defined_); return srcOffset_; } + uint32_t endOffset() const { JS_ASSERT(defined_); return endOffset_; } + Signature &sig() { return sig_; } + const Signature &sig() const { return sig_; } + Label *code() const { return code_; } + unsigned compileTime() const { return compileTime_; } + void accumulateCompileTime(unsigned ms) { compileTime_ += ms; } + }; + + class Global + { + public: + enum Which { + Variable, + ConstantLiteral, + ConstantImport, + Function, + FuncPtrTable, + FFI, + ArrayView, + MathBuiltinFunction + }; + + private: + Which which_; + union { + struct { + VarType::Which type_; + uint32_t index_; + Value literalValue_; + } varOrConst; + uint32_t funcIndex_; + uint32_t funcPtrTableIndex_; + uint32_t ffiIndex_; + ArrayBufferView::ViewType viewType_; + AsmJSMathBuiltinFunction mathBuiltinFunc_; + } u; + + friend class ModuleCompiler; + friend class js::LifoAlloc; + + Global(Which which) : which_(which) {} + + public: + Which which() const { + return which_; + } + VarType varOrConstType() const { + JS_ASSERT(which_ == Variable || which_ == ConstantLiteral || which_ == ConstantImport); + return VarType(u.varOrConst.type_); + } + uint32_t varOrConstIndex() const { + JS_ASSERT(which_ == Variable || which_ == ConstantImport); + return u.varOrConst.index_; + } + bool isConst() const { + return which_ == ConstantLiteral || which_ == ConstantImport; + } + Value constLiteralValue() const { + JS_ASSERT(which_ == ConstantLiteral); + return u.varOrConst.literalValue_; + } + uint32_t funcIndex() const { + JS_ASSERT(which_ == Function); + return u.funcIndex_; + } + uint32_t funcPtrTableIndex() const { + JS_ASSERT(which_ == FuncPtrTable); + return u.funcPtrTableIndex_; + } + unsigned ffiIndex() const { + JS_ASSERT(which_ == FFI); + return u.ffiIndex_; + } + ArrayBufferView::ViewType viewType() const { + JS_ASSERT(which_ == ArrayView); + return u.viewType_; + } + AsmJSMathBuiltinFunction mathBuiltinFunction() const { + JS_ASSERT(which_ == MathBuiltinFunction); + return u.mathBuiltinFunc_; + } + }; + + typedef js::Vector FuncPtrVector; + + class FuncPtrTable + { + Signature sig_; + uint32_t mask_; + uint32_t globalDataOffset_; + FuncPtrVector elems_; + + public: + FuncPtrTable(ExclusiveContext *cx, Signature &&sig, uint32_t mask, uint32_t gdo) + : sig_(Move(sig)), mask_(mask), globalDataOffset_(gdo), elems_(cx) + {} + + FuncPtrTable(FuncPtrTable &&rhs) + : sig_(Move(rhs.sig_)), mask_(rhs.mask_), globalDataOffset_(rhs.globalDataOffset_), + elems_(Move(rhs.elems_)) + {} + + Signature &sig() { return sig_; } + const Signature &sig() const { return sig_; } + unsigned mask() const { return mask_; } + unsigned globalDataOffset() const { return globalDataOffset_; } + + bool initialized() const { return !elems_.empty(); } + void initElems(FuncPtrVector &&elems) { elems_ = Move(elems); JS_ASSERT(initialized()); } + unsigned numElems() const { JS_ASSERT(initialized()); return elems_.length(); } + const Func &elem(unsigned i) const { return *elems_[i]; } + }; + + typedef js::Vector FuncPtrTableVector; + + class ExitDescriptor + { + PropertyName *name_; + Signature sig_; + + public: + ExitDescriptor(PropertyName *name, Signature &&sig) + : name_(name), sig_(Move(sig)) {} + ExitDescriptor(ExitDescriptor &&rhs) + : name_(rhs.name_), sig_(Move(rhs.sig_)) + {} + const Signature &sig() const { + return sig_; + } + + // ExitDescriptor is a HashPolicy: + typedef ExitDescriptor Lookup; + static HashNumber hash(const ExitDescriptor &d) { + HashNumber hn = HashGeneric(d.name_, d.sig_.retType().which()); + const VarTypeVector &args = d.sig_.args(); + for (unsigned i = 0; i < args.length(); i++) + hn = AddToHash(hn, args[i].which()); + return hn; + } + static bool match(const ExitDescriptor &lhs, const ExitDescriptor &rhs) { + return lhs.name_ == rhs.name_ && lhs.sig_ == rhs.sig_; + } + }; + + typedef HashMap ExitMap; + + struct MathBuiltin + { + enum Kind { Function, Constant }; + Kind kind; + + union { + double cst; + AsmJSMathBuiltinFunction func; + } u; + + MathBuiltin() : kind(Kind(-1)) {} + MathBuiltin(double cst) : kind(Constant) { + u.cst = cst; + } + MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) { + u.func = func; + } + }; + + private: + struct SlowFunction + { + PropertyName *name; + unsigned ms; + unsigned line; + unsigned column; + }; + + typedef HashMap MathNameMap; + typedef HashMap GlobalMap; + typedef js::Vector FuncVector; + typedef js::Vector GlobalAccessVector; + typedef js::Vector SlowFunctionVector; + + ExclusiveContext * cx_; + AsmJSParser & parser_; + + MacroAssembler masm_; + + ScopedJSDeletePtr module_; + LifoAlloc moduleLifo_; + ParseNode * moduleFunctionNode_; + PropertyName * moduleFunctionName_; + + GlobalMap globals_; + FuncVector functions_; + FuncPtrTableVector funcPtrTables_; + ExitMap exits_; + MathNameMap standardLibraryMathNames_; + Label stackOverflowLabel_; + Label interruptLabel_; + + char * errorString_; + uint32_t errorOffset_; + bool errorOverRecursed_; + + int64_t usecBefore_; + SlowFunctionVector slowFunctions_; + + DebugOnly finishedFunctionBodies_; + + bool addStandardLibraryMathName(const char *name, AsmJSMathBuiltinFunction func) { + JSAtom *atom = Atomize(cx_, name, strlen(name)); + if (!atom) + return false; + MathBuiltin builtin(func); + return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin); + } + bool addStandardLibraryMathName(const char *name, double cst) { + JSAtom *atom = Atomize(cx_, name, strlen(name)); + if (!atom) + return false; + MathBuiltin builtin(cst); + return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin); + } + + public: + ModuleCompiler(ExclusiveContext *cx, AsmJSParser &parser) + : cx_(cx), + parser_(parser), + masm_(MacroAssembler::AsmJSToken()), + moduleLifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE), + moduleFunctionNode_(parser.pc->maybeFunction), + moduleFunctionName_(nullptr), + globals_(cx), + functions_(cx), + funcPtrTables_(cx), + exits_(cx), + standardLibraryMathNames_(cx), + errorString_(nullptr), + errorOffset_(UINT32_MAX), + errorOverRecursed_(false), + usecBefore_(PRMJ_Now()), + slowFunctions_(cx), + finishedFunctionBodies_(false) + { + JS_ASSERT(moduleFunctionNode_->pn_funbox == parser.pc->sc->asFunctionBox()); + } + + ~ModuleCompiler() { + if (errorString_) { + JS_ASSERT(errorOffset_ != UINT32_MAX); + tokenStream().reportAsmJSError(errorOffset_, + JSMSG_USE_ASM_TYPE_FAIL, + errorString_); + js_free(errorString_); + } + if (errorOverRecursed_) + js_ReportOverRecursed(cx_); + + // Avoid spurious Label assertions on compilation failure. + if (!stackOverflowLabel_.bound()) + stackOverflowLabel_.bind(0); + if (!interruptLabel_.bound()) + interruptLabel_.bind(0); + } + + bool init() { + if (!globals_.init() || !exits_.init()) + return false; + + if (!standardLibraryMathNames_.init() || + !addStandardLibraryMathName("sin", AsmJSMathBuiltin_sin) || + !addStandardLibraryMathName("cos", AsmJSMathBuiltin_cos) || + !addStandardLibraryMathName("tan", AsmJSMathBuiltin_tan) || + !addStandardLibraryMathName("asin", AsmJSMathBuiltin_asin) || + !addStandardLibraryMathName("acos", AsmJSMathBuiltin_acos) || + !addStandardLibraryMathName("atan", AsmJSMathBuiltin_atan) || + !addStandardLibraryMathName("ceil", AsmJSMathBuiltin_ceil) || + !addStandardLibraryMathName("floor", AsmJSMathBuiltin_floor) || + !addStandardLibraryMathName("exp", AsmJSMathBuiltin_exp) || + !addStandardLibraryMathName("log", AsmJSMathBuiltin_log) || + !addStandardLibraryMathName("pow", AsmJSMathBuiltin_pow) || + !addStandardLibraryMathName("sqrt", AsmJSMathBuiltin_sqrt) || + !addStandardLibraryMathName("abs", AsmJSMathBuiltin_abs) || + !addStandardLibraryMathName("atan2", AsmJSMathBuiltin_atan2) || + !addStandardLibraryMathName("imul", AsmJSMathBuiltin_imul) || + !addStandardLibraryMathName("fround", AsmJSMathBuiltin_fround) || + !addStandardLibraryMathName("min", AsmJSMathBuiltin_min) || + !addStandardLibraryMathName("max", AsmJSMathBuiltin_max) || + !addStandardLibraryMathName("E", M_E) || + !addStandardLibraryMathName("LN10", M_LN10) || + !addStandardLibraryMathName("LN2", M_LN2) || + !addStandardLibraryMathName("LOG2E", M_LOG2E) || + !addStandardLibraryMathName("LOG10E", M_LOG10E) || + !addStandardLibraryMathName("PI", M_PI) || + !addStandardLibraryMathName("SQRT1_2", M_SQRT1_2) || + !addStandardLibraryMathName("SQRT2", M_SQRT2)) + { + return false; + } + + uint32_t funcStart = parser_.pc->maybeFunction->pn_body->pn_pos.begin; + uint32_t offsetToEndOfUseAsm = tokenStream().currentToken().pos.end; + + // "use strict" should be added to the source if we are in an implicit + // strict context, see also comment above addUseStrict in + // js::FunctionToString. + bool strict = parser_.pc->sc->strict && !parser_.pc->sc->hasExplicitUseStrict(); + + module_ = cx_->new_(parser_.ss, funcStart, offsetToEndOfUseAsm, strict); + if (!module_) + return false; + + return true; + } + + bool failOffset(uint32_t offset, const char *str) { + JS_ASSERT(!errorString_); + JS_ASSERT(errorOffset_ == UINT32_MAX); + JS_ASSERT(str); + errorOffset_ = offset; + errorString_ = js_strdup(cx_, str); + return false; + } + + bool fail(ParseNode *pn, const char *str) { + if (pn) + return failOffset(pn->pn_pos.begin, str); + + // The exact rooting static analysis does not perform dataflow analysis, so it believes + // that unrooted things on the stack during compilation may still be accessed after this. + // Since pn is typically only null under OOM, this suppression simply forces any GC to be + // delayed until the compilation is off the stack and more memory can be freed. + gc::AutoSuppressGC nogc(cx_); + return failOffset(tokenStream().peekTokenPos().begin, str); + } + + bool failfVA(ParseNode *pn, const char *fmt, va_list ap) { + JS_ASSERT(!errorString_); + JS_ASSERT(errorOffset_ == UINT32_MAX); + JS_ASSERT(fmt); + errorOffset_ = pn ? pn->pn_pos.begin : tokenStream().currentToken().pos.end; + errorString_ = JS_vsmprintf(fmt, ap); + return false; + } + + bool failf(ParseNode *pn, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + failfVA(pn, fmt, ap); + va_end(ap); + return false; + } + + bool failName(ParseNode *pn, const char *fmt, PropertyName *name) { + // This function is invoked without the caller properly rooting its locals. + gc::AutoSuppressGC suppress(cx_); + JSAutoByteString bytes; + if (AtomToPrintableString(cx_, name, &bytes)) + failf(pn, fmt, bytes.ptr()); + return false; + } + + bool failOverRecursed() { + errorOverRecursed_ = true; + return false; + } + + static const unsigned SLOW_FUNCTION_THRESHOLD_MS = 250; + + bool maybeReportCompileTime(const Func &func) { + if (func.compileTime() < SLOW_FUNCTION_THRESHOLD_MS) + return true; + SlowFunction sf; + sf.name = func.name(); + sf.ms = func.compileTime(); + tokenStream().srcCoords.lineNumAndColumnIndex(func.srcOffset(), &sf.line, &sf.column); + return slowFunctions_.append(sf); + } + + /*************************************************** Read-only interface */ + + ExclusiveContext *cx() const { return cx_; } + AsmJSParser &parser() const { return parser_; } + TokenStream &tokenStream() const { return parser_.tokenStream; } + MacroAssembler &masm() { return masm_; } + Label &stackOverflowLabel() { return stackOverflowLabel_; } + Label &interruptLabel() { return interruptLabel_; } + bool hasError() const { return errorString_ != nullptr; } + const AsmJSModule &module() const { return *module_.get(); } + uint32_t moduleStart() const { return module_->funcStart(); } + + ParseNode *moduleFunctionNode() const { return moduleFunctionNode_; } + PropertyName *moduleFunctionName() const { return moduleFunctionName_; } + + const Global *lookupGlobal(PropertyName *name) const { + if (GlobalMap::Ptr p = globals_.lookup(name)) + return p->value(); + return nullptr; + } + Func *lookupFunction(PropertyName *name) { + if (GlobalMap::Ptr p = globals_.lookup(name)) { + Global *value = p->value(); + if (value->which() == Global::Function) + return functions_[value->funcIndex()]; + } + return nullptr; + } + unsigned numFunctions() const { + return functions_.length(); + } + Func &function(unsigned i) { + return *functions_[i]; + } + unsigned numFuncPtrTables() const { + return funcPtrTables_.length(); + } + FuncPtrTable &funcPtrTable(unsigned i) { + return funcPtrTables_[i]; + } + bool lookupStandardLibraryMathName(PropertyName *name, MathBuiltin *mathBuiltin) const { + if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) { + *mathBuiltin = p->value(); + return true; + } + return false; + } + ExitMap::Range allExits() const { + return exits_.all(); + } + + /***************************************************** Mutable interface */ + + void initModuleFunctionName(PropertyName *name) { moduleFunctionName_ = name; } + + void initGlobalArgumentName(PropertyName *n) { module_->initGlobalArgumentName(n); } + void initImportArgumentName(PropertyName *n) { module_->initImportArgumentName(n); } + void initBufferArgumentName(PropertyName *n) { module_->initBufferArgumentName(n); } + + bool addGlobalVarInit(PropertyName *varName, VarType type, const Value &v, bool isConst) { + uint32_t index; + if (!module_->addGlobalVarInit(v, type.toCoercion(), &index)) + return false; + + Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable; + Global *global = moduleLifo_.new_(which); + if (!global) + return false; + global->u.varOrConst.index_ = index; + global->u.varOrConst.type_ = type.which(); + if (isConst) + global->u.varOrConst.literalValue_ = v; + + return globals_.putNew(varName, global); + } + bool addGlobalVarImport(PropertyName *varName, PropertyName *fieldName, AsmJSCoercion coercion, + bool isConst) { + uint32_t index; + if (!module_->addGlobalVarImport(fieldName, coercion, &index)) + return false; + + Global::Which which = isConst ? Global::ConstantImport : Global::Variable; + Global *global = moduleLifo_.new_(which); + if (!global) + return false; + global->u.varOrConst.index_ = index; + global->u.varOrConst.type_ = VarType(coercion).which(); + + return globals_.putNew(varName, global); + } + bool addFunction(PropertyName *name, Signature &&sig, Func **func) { + JS_ASSERT(!finishedFunctionBodies_); + Global *global = moduleLifo_.new_(Global::Function); + if (!global) + return false; + global->u.funcIndex_ = functions_.length(); + if (!globals_.putNew(name, global)) + return false; + Label *code = moduleLifo_.new_