/* -*- 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 "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/HelperThreads.h" #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)) {} MOZ_IMPLICIT 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)) {} MOZ_IMPLICIT RetType(Which w) : which_(w) {} MOZ_IMPLICIT 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)) {} MOZ_IMPLICIT VarType(Which w) : which_(w) {} MOZ_IMPLICIT 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: explicit 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: explicit 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; explicit 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)) {} explicit MathBuiltin(double cst) : kind(Constant) { u.cst = cst; } explicit 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_