| /* -*- 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 "jsmath.h" |
| #include "jsworkers.h" |
| #include "prmjtime.h" |
| |
| #include "frontend/ParseNode.h" |
| #include "jit/AsmJS.h" |
| #include "jit/AsmJSModule.h" |
| |
| #include "frontend/ParseNode-inl.h" |
| |
| #include "jit/PerfSpewer.h" |
| #include "jit/CodeGenerator.h" |
| #include "jit/MIR.h" |
| #include "jit/MIRGraph.h" |
| |
| #ifdef MOZ_VTUNE |
| # include "jitprofiling.h" |
| #endif |
| |
| using namespace js; |
| using namespace js::frontend; |
| using namespace js::jit; |
| using namespace mozilla; |
| |
| /*****************************************************************************/ |
| // 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 * |
| 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)); |
| 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 JSAtom * |
| StringAtom(ParseNode *pn) |
| { |
| JS_ASSERT(pn->isKind(PNK_STRING)); |
| return pn->pn_atom; |
| } |
| |
| 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<LoopControlStatement>().label(); |
| } |
| |
| static inline PropertyName * |
| LabeledStatementLabel(ParseNode *pn) |
| { |
| return pn->as<LabeledStatement>().label(); |
| } |
| |
| static inline ParseNode * |
| LabeledStatementStatement(ParseNode *pn) |
| { |
| return pn->as<LabeledStatement>().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 NULL; |
| } |
| |
| static inline ParseNode * |
| FunctionArgsList(ParseNode *fn, unsigned *numFormals) |
| { |
| JS_ASSERT(fn->isKind(PNK_FUNCTION)); |
| ParseNode *argsBody = fn->pn_body; |
| JS_ASSERT(argsBody->isKind(PNK_ARGSBODY)); |
| *numFormals = argsBody->pn_count - 1; |
| return ListHead(argsBody); |
| } |
| |
| static inline unsigned |
| FunctionNumFormals(ParseNode *fn) |
| { |
| unsigned numFormals; |
| FunctionArgsList(fn, &numFormals); |
| return numFormals; |
| } |
| |
| static inline bool |
| FunctionHasStatementList(ParseNode *fn) |
| { |
| JS_ASSERT(fn->isKind(PNK_FUNCTION)); |
| ParseNode *argsBody = fn->pn_body; |
| JS_ASSERT(argsBody->isKind(PNK_ARGSBODY)); |
| ParseNode *body = argsBody->last(); |
| return body->isKind(PNK_STATEMENTLIST); |
| } |
| |
| static inline ParseNode * |
| FunctionStatementList(ParseNode *fn) |
| { |
| JS_ASSERT(FunctionHasStatementList(fn)); |
| return fn->pn_body->last(); |
| } |
| |
| static inline ParseNode * |
| FunctionLastReturnStatementOrNull(ParseNode *fn) |
| { |
| ParseNode *listIter = ListHead(FunctionStatementList(fn)); |
| ParseNode *lastReturn = NULL; |
| while (listIter) { |
| if (listIter->isKind(PNK_RETURN)) |
| lastReturn = listIter; |
| listIter = listIter->pn_next; |
| } |
| return lastReturn; |
| } |
| |
| static inline bool |
| IsNormalObjectField(JSContext *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(JSContext *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 ParseNode * |
| SkipEmptyStatements(ParseNode *pn) |
| { |
| while (pn && pn->isKind(PNK_SEMI) && !UnaryKid(pn)) |
| pn = pn->pn_next; |
| return pn; |
| } |
| |
| static inline ParseNode * |
| NextNonEmptyStatement(ParseNode *pn) |
| { |
| return SkipEmptyStatements(pn->pn_next); |
| } |
| |
| /*****************************************************************************/ |
| |
| // Respresents the type of a general asm.js expression. |
| class Type |
| { |
| public: |
| enum Which { |
| Double, |
| Doublish, |
| Fixnum, |
| Int, |
| Signed, |
| Unsigned, |
| Intish, |
| Void, |
| Unknown |
| }; |
| |
| 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 isDoublish() const { |
| return isDouble() || which_ == Doublish; |
| } |
| |
| bool isVoid() const { |
| return which_ == Void; |
| } |
| |
| bool isExtern() const { |
| return isDouble() || isSigned(); |
| } |
| |
| MIRType toMIRType() const { |
| switch (which_) { |
| case Double: |
| case Doublish: |
| return MIRType_Double; |
| case Fixnum: |
| case Int: |
| case Signed: |
| case Unsigned: |
| case Intish: |
| return MIRType_Int32; |
| case Void: |
| case Unknown: |
| return MIRType_None; |
| } |
| JS_NOT_REACHED("Invalid Type"); |
| return MIRType_None; |
| } |
| |
| const char *toChars() const { |
| switch (which_) { |
| case Double: return "double"; |
| case Doublish: return "doublish"; |
| case Fixnum: return "fixnum"; |
| case Int: return "int"; |
| case Signed: return "signed"; |
| case Unsigned: return "unsigned"; |
| case Intish: return "intish"; |
| case Void: return "void"; |
| case Unknown: return "unknown"; |
| } |
| JS_NOT_REACHED("Invalid Type"); |
| return ""; |
| } |
| }; |
| |
| // 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 |
| }; |
| |
| private: |
| Which which_; |
| |
| public: |
| RetType() {} |
| RetType(Which w) : which_(w) {} |
| RetType(AsmJSCoercion coercion) { |
| switch (coercion) { |
| case AsmJS_ToInt32: which_ = Signed; break; |
| case AsmJS_ToNumber: which_ = Double; 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 Double: return AsmJSModule::Return_Double; |
| } |
| JS_NOT_REACHED("Unexpected return type"); |
| return AsmJSModule::Return_Void; |
| } |
| MIRType toMIRType() const { |
| switch (which_) { |
| case Void: return MIRType_None; |
| case Signed: return MIRType_Int32; |
| case Double: return MIRType_Double; |
| } |
| JS_NOT_REACHED("Unexpected return type"); |
| return MIRType_None; |
| } |
| bool operator==(RetType rhs) const { return which_ == rhs.which_; } |
| bool operator!=(RetType rhs) const { return which_ != rhs.which_; } |
| }; |
| |
| // Implements <: (subtype) operator when the rhs is an RetType |
| static inline bool |
| operator<=(Type lhs, RetType rhs) |
| { |
| switch (rhs.which()) { |
| case RetType::Signed: return lhs.isSigned(); |
| case RetType::Double: return lhs == Type::Double; |
| case RetType::Void: return lhs == Type::Void; |
| } |
| JS_NOT_REACHED("Unexpected rhs type"); |
| return false; |
| } |
| |
| // 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 |
| }; |
| |
| 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; |
| } |
| } |
| Which which() const { |
| return which_; |
| } |
| Type toType() const { |
| return Type::Which(which_); |
| } |
| MIRType toMIRType() const { |
| return which_ == Int ? MIRType_Int32 : MIRType_Double; |
| } |
| AsmJSCoercion toCoercion() const { |
| return which_ == Int ? AsmJS_ToInt32 : AsmJS_ToNumber; |
| } |
| static VarType FromMIRType(MIRType type) { |
| JS_ASSERT(type == MIRType_Int32 || type == MIRType_Double); |
| return type == MIRType_Int32 ? Int : Double; |
| } |
| bool operator==(VarType rhs) const { return which_ == rhs.which_; } |
| bool operator!=(VarType rhs) const { return which_ != rhs.which_; } |
| }; |
| |
| // 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(); |
| } |
| JS_NOT_REACHED("Unexpected rhs type"); |
| return false; |
| } |
| |
| // Passed from parent expressions to child expressions to indicate if and how |
| // the child expression's result will be coerced. While most type checking |
| // occurs bottom-up (with child expressions returning the type of the result |
| // and parents checking these types), FFI calls naturally want to know the |
| // parent's context to determine the appropriate result type. If a parent |
| // passes NoCoercion to an FFI all, then the FFI's return type will be "Void" |
| // which will cause a type error if the result is used. |
| // |
| // The other application of Use is to support the asm.js type rule which |
| // allows (a-b+c-d+e)|0 without intermediate conversions. The type system has |
| // only binary +/- nodes so we simulate the n-ary expression by having the |
| // outer parent +/- expression pass in Use::AddOrSub so that the inner |
| // expression knows to return type Int instead of Intish. |
| // |
| class Use |
| { |
| public: |
| enum Which { |
| Normal, |
| AddOrSub |
| }; |
| |
| private: |
| Which which_; |
| unsigned *pcount_; |
| |
| public: |
| Use() |
| : which_(Which(-1)), pcount_(NULL) {} |
| Use(Which w) |
| : which_(w), pcount_(NULL) { JS_ASSERT(w != AddOrSub); } |
| Use(unsigned *pcount) |
| : which_(AddOrSub), pcount_(pcount) {} |
| Which which() const { |
| return which_; |
| } |
| unsigned &addOrSubCount() const { |
| JS_ASSERT(which_ == AddOrSub); |
| return *pcount_; |
| } |
| bool operator==(Use rhs) const { return which_ == rhs.which_; } |
| bool operator!=(Use rhs) const { return which_ != rhs.which_; } |
| }; |
| |
| /*****************************************************************************/ |
| // Numeric literal utilities |
| |
| // Represents the type and value of an asm.js numeric literal. |
| // |
| // A literal is a double iff the literal contains an exponent or decimal point |
| // (even if the fractional part is 0). Otherwise, integers may be classified: |
| // fixnum: [0, 2^31) |
| // negative int: [-2^31, 0) |
| // big unsigned: [2^31, 2^32) |
| // out of range: otherwise |
| class NumLit |
| { |
| public: |
| enum Which { |
| Fixnum = Type::Fixnum, |
| NegativeInt = Type::Signed, |
| BigUnsigned = Type::Unsigned, |
| Double = Type::Double, |
| OutOfRangeInt = -1 |
| }; |
| |
| private: |
| Which which_; |
| Value v_; |
| |
| public: |
| NumLit(Which w, Value v) |
| : which_(w), v_(v) |
| {} |
| |
| Which which() const { |
| return which_; |
| } |
| |
| int32_t toInt32() const { |
| JS_ASSERT(which_ == Fixnum || which_ == NegativeInt || which_ == BigUnsigned); |
| return v_.toInt32(); |
| } |
| |
| double toDouble() const { |
| return v_.toDouble(); |
| } |
| |
| Type type() const { |
| JS_ASSERT(which_ != OutOfRangeInt); |
| return Type::Which(which_); |
| } |
| |
| Value value() const { |
| JS_ASSERT(which_ != OutOfRangeInt); |
| return v_; |
| } |
| }; |
| |
| // Note: '-' is never rolled into the number; numbers are always positive and |
| // negations must be applied manually. |
| static bool |
| IsNumericLiteral(ParseNode *pn) |
| { |
| return pn->isKind(PNK_NUMBER) || |
| (pn->isKind(PNK_NEG) && UnaryKid(pn)->isKind(PNK_NUMBER)); |
| } |
| |
| static NumLit |
| ExtractNumericLiteral(ParseNode *pn) |
| { |
| JS_ASSERT(IsNumericLiteral(pn)); |
| ParseNode *numberNode; |
| double d; |
| if (pn->isKind(PNK_NEG)) { |
| numberNode = UnaryKid(pn); |
| d = -NumberNodeValue(numberNode); |
| } else { |
| numberNode = pn; |
| d = NumberNodeValue(numberNode); |
| } |
| |
| if (NumberNodeHasFrac(numberNode)) |
| return NumLit(NumLit::Double, DoubleValue(d)); |
| |
| int64_t i64 = int64_t(d); |
| if (d != double(i64)) |
| return NumLit(NumLit::OutOfRangeInt, UndefinedValue()); |
| |
| if (i64 >= 0) { |
| if (i64 <= INT32_MAX) |
| return NumLit(NumLit::Fixnum, Int32Value(i64)); |
| if (i64 <= UINT32_MAX) |
| return NumLit(NumLit::BigUnsigned, Int32Value(uint32_t(i64))); |
| return NumLit(NumLit::OutOfRangeInt, UndefinedValue()); |
| } |
| if (i64 >= INT32_MIN) |
| return NumLit(NumLit::NegativeInt, Int32Value(i64)); |
| return NumLit(NumLit::OutOfRangeInt, UndefinedValue()); |
| } |
| |
| static inline bool |
| IsLiteralUint32(ParseNode *pn, uint32_t *u32) |
| { |
| if (!IsNumericLiteral(pn)) |
| return false; |
| |
| NumLit literal = ExtractNumericLiteral(pn); |
| switch (literal.which()) { |
| case NumLit::Fixnum: |
| case NumLit::BigUnsigned: |
| *u32 = uint32_t(literal.toInt32()); |
| return true; |
| case NumLit::NegativeInt: |
| case NumLit::Double: |
| case NumLit::OutOfRangeInt: |
| return false; |
| } |
| |
| JS_NOT_REACHED("Bad literal type"); |
| } |
| |
| static inline bool |
| IsBits32(ParseNode *pn, int32_t i) |
| { |
| if (!IsNumericLiteral(pn)) |
| return false; |
| |
| NumLit literal = ExtractNumericLiteral(pn); |
| switch (literal.which()) { |
| case NumLit::Fixnum: |
| case NumLit::BigUnsigned: |
| case NumLit::NegativeInt: |
| return literal.toInt32() == i; |
| case NumLit::Double: |
| case NumLit::OutOfRangeInt: |
| return false; |
| } |
| |
| JS_NOT_REACHED("Bad literal type"); |
| } |
| |
| /*****************************************************************************/ |
| // 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: |
| case ArrayBufferView::TYPE_FLOAT64: |
| return Type::Doublish; |
| default:; |
| } |
| JS_NOT_REACHED("Unexpected array type"); |
| return Type(); |
| } |
| |
| enum ArrayStoreEnum { |
| ArrayStore_Intish, |
| ArrayStore_Doublish |
| }; |
| |
| static ArrayStoreEnum |
| TypedArrayStoreType(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 ArrayStore_Intish; |
| case ArrayBufferView::TYPE_FLOAT32: |
| case ArrayBufferView::TYPE_FLOAT64: |
| return ArrayStore_Doublish; |
| default:; |
| } |
| JS_NOT_REACHED("Unexpected array type"); |
| return ArrayStore_Doublish; |
| } |
| |
| /*****************************************************************************/ |
| |
| typedef Vector<PropertyName*,1> LabelVector; |
| typedef Vector<MBasicBlock*,8> 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<Exit> 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 |
| { |
| ParseNode *fn_; |
| ParseNode *body_; |
| MIRTypeVector argTypes_; |
| RetType returnType_; |
| mutable Label code_; |
| unsigned compileTime_; |
| |
| public: |
| Func(ParseNode *fn, ParseNode *body, MoveRef<MIRTypeVector> types, RetType returnType) |
| : fn_(fn), |
| body_(body), |
| argTypes_(types), |
| returnType_(returnType), |
| code_(), |
| compileTime_(0) |
| {} |
| |
| Func(MoveRef<Func> rhs) |
| : fn_(rhs->fn_), |
| body_(rhs->body_), |
| argTypes_(Move(rhs->argTypes_)), |
| returnType_(rhs->returnType_), |
| code_(rhs->code_), |
| compileTime_(rhs->compileTime_) |
| {} |
| |
| ~Func() |
| { |
| // Avoid spurious Label assertions on compilation failure. |
| if (!code_.bound()) |
| code_.bind(0); |
| } |
| |
| ParseNode *fn() const { return fn_; } |
| ParseNode *body() const { return body_; } |
| unsigned numArgs() const { return argTypes_.length(); } |
| VarType argType(unsigned i) const { return VarType::FromMIRType(argTypes_[i]); } |
| const MIRTypeVector &argMIRTypes() const { return argTypes_; } |
| RetType returnType() const { return returnType_; } |
| Label *codeLabel() const { return &code_; } |
| unsigned compileTime() const { return compileTime_; } |
| void accumulateCompileTime(unsigned ms) { compileTime_ += ms; } |
| }; |
| |
| class Global |
| { |
| public: |
| enum Which { Variable, Function, FuncPtrTable, FFI, ArrayView, MathBuiltin, Constant }; |
| |
| private: |
| Which which_; |
| union { |
| struct { |
| uint32_t index_; |
| VarType::Which type_; |
| } var; |
| uint32_t funcIndex_; |
| uint32_t funcPtrTableIndex_; |
| uint32_t ffiIndex_; |
| ArrayBufferView::ViewType viewType_; |
| AsmJSMathBuiltin mathBuiltin_; |
| double constant_; |
| } u; |
| |
| friend class ModuleCompiler; |
| |
| Global(Which which) : which_(which) {} |
| |
| public: |
| Which which() const { |
| return which_; |
| } |
| VarType varType() const { |
| JS_ASSERT(which_ == Variable); |
| return VarType(u.var.type_); |
| } |
| uint32_t varIndex() const { |
| JS_ASSERT(which_ == Variable); |
| return u.var.index_; |
| } |
| 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_; |
| } |
| AsmJSMathBuiltin mathBuiltin() const { |
| JS_ASSERT(which_ == MathBuiltin); |
| return u.mathBuiltin_; |
| } |
| double constant() const { |
| JS_ASSERT(which_ == Constant); |
| return u.constant_; |
| } |
| }; |
| |
| typedef Vector<const Func*> FuncPtrVector; |
| |
| class FuncPtrTable |
| { |
| FuncPtrVector elems_; |
| unsigned baseIndex_; |
| |
| public: |
| FuncPtrTable(MoveRef<FuncPtrVector> elems, unsigned baseIndex) |
| : elems_(elems), baseIndex_(baseIndex) {} |
| FuncPtrTable(MoveRef<FuncPtrTable> rhs) |
| : elems_(Move(rhs->elems_)), baseIndex_(rhs->baseIndex_) {} |
| |
| const Func &sig() const { return *elems_[0]; } |
| unsigned numElems() const { return elems_.length(); } |
| const Func &elem(unsigned i) const { return *elems_[i]; } |
| unsigned baseIndex() const { return baseIndex_; } |
| unsigned mask() const { JS_ASSERT(IsPowerOfTwo(numElems())); return numElems() - 1; } |
| }; |
| |
| typedef Vector<FuncPtrTable> FuncPtrTableVector; |
| |
| class ExitDescriptor |
| { |
| PropertyName *name_; |
| MIRTypeVector argTypes_; |
| RetType retType_; |
| |
| public: |
| ExitDescriptor(PropertyName *name, MoveRef<MIRTypeVector> argTypes, RetType retType) |
| : name_(name), |
| argTypes_(argTypes), |
| retType_(retType) |
| {} |
| ExitDescriptor(MoveRef<ExitDescriptor> rhs) |
| : name_(rhs->name_), |
| argTypes_(Move(rhs->argTypes_)), |
| retType_(rhs->retType_) |
| {} |
| const MIRTypeVector &argTypes() const { |
| return argTypes_; |
| } |
| RetType retType() const { |
| return retType_; |
| } |
| |
| // ExitDescriptor is a HashPolicy: |
| typedef ExitDescriptor Lookup; |
| static HashNumber hash(const ExitDescriptor &d) { |
| HashNumber hn = HashGeneric(d.name_, d.retType_.which()); |
| for (unsigned i = 0; i < d.argTypes_.length(); i++) |
| hn = AddToHash(hn, d.argTypes_[i]); |
| return hn; |
| } |
| static bool match(const ExitDescriptor &lhs, const ExitDescriptor &rhs) { |
| if (lhs.name_ != rhs.name_ || |
| lhs.argTypes_.length() != rhs.argTypes_.length() || |
| lhs.retType_ != rhs.retType_) |
| { |
| return false; |
| } |
| for (unsigned i = 0; i < lhs.argTypes_.length(); i++) { |
| if (lhs.argTypes_[i] != rhs.argTypes_[i]) |
| return false; |
| } |
| return true; |
| } |
| }; |
| |
| typedef HashMap<ExitDescriptor, unsigned, ExitDescriptor, ContextAllocPolicy> ExitMap; |
| |
| private: |
| struct SlowFunction |
| { |
| PropertyName *name; |
| unsigned ms; |
| unsigned line; |
| unsigned column; |
| }; |
| |
| typedef HashMap<PropertyName*, AsmJSMathBuiltin> MathNameMap; |
| typedef HashMap<PropertyName*, Global> GlobalMap; |
| typedef Vector<Func> FuncVector; |
| typedef Vector<AsmJSGlobalAccess> GlobalAccessVector; |
| typedef Vector<SlowFunction> SlowFunctionVector; |
| |
| JSContext * cx_; |
| MacroAssembler masm_; |
| |
| ScopedJSDeletePtr<AsmJSModule> module_; |
| |
| PropertyName * moduleFunctionName_; |
| |
| GlobalMap globals_; |
| FuncVector functions_; |
| FuncPtrTableVector funcPtrTables_; |
| ExitMap exits_; |
| MathNameMap standardLibraryMathNames_; |
| GlobalAccessVector globalAccesses_; |
| Label stackOverflowLabel_; |
| Label operationCallbackLabel_; |
| |
| char * errorString_; |
| ParseNode * errorNode_; |
| |
| int64_t usecBefore_; |
| SlowFunctionVector slowFunctions_; |
| |
| TokenStream & tokenStream_; |
| |
| DebugOnly<int> currentPass_; |
| |
| bool addStandardLibraryMathName(const char *name, AsmJSMathBuiltin builtin) { |
| JSAtom *atom = Atomize(cx_, name, strlen(name)); |
| if (!atom) |
| return false; |
| return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin); |
| } |
| |
| public: |
| ModuleCompiler(JSContext *cx, TokenStream &ts) |
| : cx_(cx), |
| masm_(cx), |
| moduleFunctionName_(NULL), |
| globals_(cx), |
| functions_(cx), |
| funcPtrTables_(cx), |
| exits_(cx), |
| standardLibraryMathNames_(cx), |
| globalAccesses_(cx), |
| errorString_(NULL), |
| errorNode_(NULL), |
| usecBefore_(PRMJ_Now()), |
| slowFunctions_(cx), |
| tokenStream_(ts), |
| currentPass_(1) |
| {} |
| |
| ~ModuleCompiler() { |
| if (errorString_) { |
| tokenStream_.reportAsmJSError(errorNode_->pn_pos.begin, |
| JSMSG_USE_ASM_TYPE_FAIL, |
| errorString_); |
| js_free(errorString_); |
| } |
| |
| // Avoid spurious Label assertions on compilation failure. |
| if (!stackOverflowLabel_.bound()) |
| stackOverflowLabel_.bind(0); |
| if (!operationCallbackLabel_.bound()) |
| operationCallbackLabel_.bind(0); |
| } |
| |
| bool init() { |
| if (!cx_->compartment()->ensureIonCompartmentExists(cx_)) |
| return false; |
| |
| 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)) |
| { |
| return false; |
| } |
| |
| module_ = cx_->new_<AsmJSModule>(cx_); |
| if (!module_) |
| return false; |
| |
| return true; |
| } |
| |
| bool fail(ParseNode *pn, const char *str) { |
| JS_ASSERT(!errorString_); |
| JS_ASSERT(!errorNode_); |
| JS_ASSERT(str); |
| JS_ASSERT(pn); |
| errorNode_ = pn; |
| errorString_ = js_strdup(cx_, str); |
| return false; |
| } |
| |
| bool failfVA(ParseNode *pn, const char *fmt, va_list ap) { |
| JS_ASSERT(!errorString_); |
| JS_ASSERT(!errorNode_); |
| JS_ASSERT(fmt); |
| JS_ASSERT(pn); |
| errorNode_ = pn; |
| 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) { |
| JSAutoByteString bytes(cx_, name); |
| if (bytes.ptr()) |
| failf(pn, fmt, bytes.ptr()); |
| return false; |
| } |
| |
| static const unsigned SLOW_FUNCTION_THRESHOLD_MS = 250; |
| |
| bool maybeReportCompileTime(ParseNode *fn, unsigned ms) { |
| if (ms < SLOW_FUNCTION_THRESHOLD_MS) |
| return true; |
| SlowFunction sf; |
| sf.name = FunctionName(fn); |
| sf.ms = ms; |
| tokenStream_.srcCoords.lineNumAndColumnIndex(fn->pn_pos.begin, &sf.line, &sf.column); |
| return slowFunctions_.append(sf); |
| } |
| |
| /*************************************************** Read-only interface */ |
| |
| JSContext *cx() const { return cx_; } |
| MacroAssembler &masm() { return masm_; } |
| Label &stackOverflowLabel() { return stackOverflowLabel_; } |
| Label &operationCallbackLabel() { return operationCallbackLabel_; } |
| bool hasError() const { return errorString_ != NULL; } |
| const AsmJSModule &module() const { return *module_.get(); } |
| |
| PropertyName *moduleFunctionName() const { return moduleFunctionName_; } |
| |
| const Global *lookupGlobal(PropertyName *name) const { |
| if (GlobalMap::Ptr p = globals_.lookup(name)) |
| return &p->value; |
| return NULL; |
| } |
| const FuncPtrTable *lookupFuncPtrTable(PropertyName *name) const { |
| if (GlobalMap::Ptr p = globals_.lookup(name)) { |
| if (p->value.which() == Global::FuncPtrTable) |
| return &funcPtrTables_[p->value.funcPtrTableIndex()]; |
| } |
| return NULL; |
| } |
| const Func *lookupFunction(PropertyName *name) const { |
| if (GlobalMap::Ptr p = globals_.lookup(name)) { |
| if (p->value.which() == Global::Function) |
| return &functions_[p->value.funcIndex()]; |
| } |
| return NULL; |
| } |
| unsigned numFunctions() const { |
| return functions_.length(); |
| } |
| const Func &function(unsigned i) const { |
| return functions_[i]; |
| } |
| bool lookupStandardLibraryMathName(PropertyName *name, AsmJSMathBuiltin *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 *n) { moduleFunctionName_ = n; } |
| |
| void initGlobalArgumentName(PropertyName *n) { module_->initGlobalArgumentName(n); } |
| void initImportArgumentName(PropertyName *n) { module_->initImportArgumentName(n); } |
| void initBufferArgumentName(PropertyName *n) { module_->initBufferArgumentName(n); } |
| |
| bool addGlobalVarInitConstant(PropertyName *varName, VarType type, const Value &v) { |
| JS_ASSERT(currentPass_ == 1); |
| Global g(Global::Variable); |
| uint32_t index; |
| if (!module_->addGlobalVarInitConstant(v, &index)) |
| return false; |
| g.u.var.index_ = index; |
| g.u.var.type_ = type.which(); |
| return globals_.putNew(varName, g); |
| } |
| bool addGlobalVarImport(PropertyName *varName, PropertyName *fieldName, AsmJSCoercion coercion) |
| { |
| JS_ASSERT(currentPass_ == 1); |
| Global g(Global::Variable); |
| uint32_t index; |
| if (!module_->addGlobalVarImport(fieldName, coercion, &index)) |
| return false; |
| g.u.var.index_ = index; |
| g.u.var.type_ = VarType(coercion).which(); |
| return globals_.putNew(varName, g); |
| } |
| bool addFunction(MoveRef<Func> func) { |
| JS_ASSERT(currentPass_ == 1); |
| Global g(Global::Function); |
| g.u.funcIndex_ = functions_.length(); |
| if (!globals_.putNew(FunctionName(func->fn()), g)) |
| return false; |
| return functions_.append(func); |
| } |
| bool addFuncPtrTable(PropertyName *varName, MoveRef<FuncPtrVector> funcPtrs) { |
| JS_ASSERT(currentPass_ == 1); |
| Global g(Global::FuncPtrTable); |
| g.u.funcPtrTableIndex_ = funcPtrTables_.length(); |
| if (!globals_.putNew(varName, g)) |
| return false; |
| FuncPtrTable table(funcPtrs, module_->numFuncPtrTableElems()); |
| if (!module_->incrementNumFuncPtrTableElems(table.numElems())) |
| return false; |
| return funcPtrTables_.append(Move(table)); |
| } |
| bool addFFI(PropertyName *varName, PropertyName *field) { |
| JS_ASSERT(currentPass_ == 1); |
| Global g(Global::FFI); |
| uint32_t index; |
| if (!module_->addFFI(field, &index)) |
| return false; |
| g.u.ffiIndex_ = index; |
| return globals_.putNew(varName, g); |
| } |
| bool addArrayView(PropertyName *varName, ArrayBufferView::ViewType vt, PropertyName *fieldName) { |
| JS_ASSERT(currentPass_ == 1); |
| Global g(Global::ArrayView); |
| if (!module_->addArrayView(vt, fieldName)) |
| return false; |
| g.u.viewType_ = vt; |
| return globals_.putNew(varName, g); |
| } |
| bool addMathBuiltin(PropertyName *varName, AsmJSMathBuiltin mathBuiltin, PropertyName *fieldName) { |
| JS_ASSERT(currentPass_ == 1); |
| if (!module_->addMathBuiltin(mathBuiltin, fieldName)) |
| return false; |
| Global g(Global::MathBuiltin); |
| g.u.mathBuiltin_ = mathBuiltin; |
| return globals_.putNew(varName, g); |
| } |
| bool addGlobalConstant(PropertyName *varName, double constant, PropertyName *fieldName) { |
| JS_ASSERT(currentPass_ == 1); |
| if (!module_->addGlobalConstant(constant, fieldName)) |
| return false; |
| Global g(Global::Constant); |
| g.u.constant_ = constant; |
| return globals_.putNew(varName, g); |
| } |
| bool collectAccesses(MIRGenerator &gen) { |
| #ifdef JS_CPU_ARM |
| if (!module_->addBoundsChecks(gen.asmBoundsChecks())) |
| return false; |
| #else |
| if (!module_->addHeapAccesses(gen.heapAccesses())) |
| return false; |
| #endif |
| if (!globalAccesses_.append(gen.globalAccesses())) |
| return false; |
| return true; |
| } |
| bool addGlobalAccess(AsmJSGlobalAccess access) { |
| return globalAccesses_.append(access); |
| } |
| bool addExportedFunction(const Func *func, PropertyName *maybeFieldName) { |
| JS_ASSERT(currentPass_ == 1); |
| AsmJSModule::ArgCoercionVector argCoercions; |
| if (!argCoercions.resize(func->numArgs())) |
| return false; |
| for (unsigned i = 0; i < func->numArgs(); i++) |
| argCoercions[i] = func->argType(i).toCoercion(); |
| AsmJSModule::ReturnType returnType = func->returnType().toModuleReturnType(); |
| return module_->addExportedFunction(FunctionObject(func->fn()), maybeFieldName, |
| Move(argCoercions), returnType); |
| } |
| |
| #ifdef MOZ_VTUNE |
| bool trackProfiledFunction(const Func &func, unsigned endCodeOffset) { |
| JSAtom *name = FunctionName(func.fn()); |
| unsigned startCodeOffset = func.codeLabel()->offset(); |
| return module_->trackProfiledFunction(name, startCodeOffset, endCodeOffset); |
| } |
| #endif |
| |
| void setFirstPassComplete() { |
| JS_ASSERT(currentPass_ == 1); |
| currentPass_ = 2; |
| } |
| |
| Func &function(unsigned funcIndex) { |
| JS_ASSERT(currentPass_ == 2); |
| return functions_[funcIndex]; |
| } |
| bool addExit(unsigned ffiIndex, PropertyName *name, MoveRef<MIRTypeVector> argTypes, |
| RetType retType, unsigned *exitIndex) |
| { |
| JS_ASSERT(currentPass_ == 2); |
| ExitDescriptor exitDescriptor(name, argTypes, retType); |
| ExitMap::AddPtr p = exits_.lookupForAdd(exitDescriptor); |
| if (p) { |
| *exitIndex = p->value; |
| return true; |
| } |
| if (!module_->addExit(ffiIndex, exitIndex)) |
| return false; |
| return exits_.add(p, Move(exitDescriptor), *exitIndex); |
| } |
| bool addFunctionCounts(IonScriptCounts *counts) { |
| return module_->addFunctionCounts(counts); |
| } |
| |
| void setSecondPassComplete() { |
| JS_ASSERT(currentPass_ == 2); |
| masm_.align(AsmJSPageSize); |
| module_->setFunctionBytes(masm_.size()); |
| currentPass_ = 3; |
| } |
| |
| void setInterpExitOffset(unsigned exitIndex) { |
| JS_ASSERT(currentPass_ == 3); |
| #if defined(JS_CPU_ARM) |
| masm_.flush(); |
| #endif |
| module_->exit(exitIndex).initInterpOffset(masm_.size()); |
| } |
| void setIonExitOffset(unsigned exitIndex) { |
| JS_ASSERT(currentPass_ == 3); |
| #if defined(JS_CPU_ARM) |
| masm_.flush(); |
| #endif |
| module_->exit(exitIndex).initIonOffset(masm_.size()); |
| } |
| void setEntryOffset(unsigned exportIndex) { |
| JS_ASSERT(currentPass_ == 3); |
| #if defined(JS_CPU_ARM) |
| masm_.flush(); |
| #endif |
| module_->exportedFunction(exportIndex).initCodeOffset(masm_.size()); |
| } |
| |
| void buildCompilationTimeReport(ScopedJSFreePtr<char> *out) { |
| int msTotal = 0; |
| ScopedJSFreePtr<char> slowFuns; |
| #ifndef JS_MORE_DETERMINISTIC |
| int64_t usecAfter = PRMJ_Now(); |
| msTotal = (usecAfter - usecBefore_) / PRMJ_USEC_PER_MSEC; |
| if (!slowFunctions_.empty()) { |
| slowFuns.reset(JS_smprintf("; %d functions compiled slowly: ", slowFunctions_.length())); |
| if (!slowFuns) |
| return; |
| for (unsigned i = 0; i < slowFunctions_.length(); i++) { |
| SlowFunction &func = slowFunctions_[i]; |
| JSAutoByteString name; |
| if (!js_AtomToPrintableString(cx_, func.name, &name)) |
| return; |
| slowFuns.reset(JS_smprintf("%s%s:%u:%u (%ums)%s", slowFuns.get(), |
| name.ptr(), func.line, func.column, func.ms, |
| i+1 < slowFunctions_.length() ? ", " : "")); |
| if (!slowFuns) |
| return; |
| } |
| } |
| #endif |
| out->reset(JS_smprintf("total compilation time %dms%s", |
| msTotal, slowFuns ? slowFuns.get() : "")); |
| } |
| |
| bool finish(ScopedJSDeletePtr<AsmJSModule> *module) { |
| // After finishing, the only valid operation on an ModuleCompiler is |
| // destruction. |
| JS_ASSERT(currentPass_ == 3); |
| currentPass_ = -1; |
| |
| // Finish the code section. |
| masm_.finish(); |
| if (masm_.oom()) |
| return false; |
| |
| // The global data section sits immediately after the executable (and |
| // other) data allocated by the MacroAssembler. Round up bytesNeeded so |
| // that doubles/pointers stay aligned. |
| size_t codeBytes = AlignBytes(masm_.bytesNeeded(), sizeof(double)); |
| size_t totalBytes = codeBytes + module_->globalDataBytes(); |
| |
| // The code must be page aligned, so include extra space so that we can |
| // AlignBytes the allocation result below. |
| size_t allocedBytes = totalBytes + AsmJSPageSize; |
| |
| // Allocate the slab of memory. |
| JSC::ExecutableAllocator *execAlloc = cx_->compartment()->ionCompartment()->execAlloc(); |
| JSC::ExecutablePool *pool; |
| uint8_t *unalignedBytes = (uint8_t*)execAlloc->alloc(allocedBytes, &pool, JSC::ASMJS_CODE); |
| if (!unalignedBytes) |
| return false; |
| uint8_t *code = (uint8_t*)AlignBytes((uintptr_t)unalignedBytes, AsmJSPageSize); |
| |
| // The ExecutablePool owns the memory and must be released by the AsmJSModule. |
| module_->takeOwnership(pool, code, codeBytes, totalBytes); |
| |
| // Copy the buffer into executable memory (c.f. IonCode::copyFrom). |
| masm_.executableCopy(code); |
| masm_.processCodeLabels(code); |
| JS_ASSERT(masm_.jumpRelocationTableBytes() == 0); |
| JS_ASSERT(masm_.dataRelocationTableBytes() == 0); |
| JS_ASSERT(masm_.preBarrierTableBytes() == 0); |
| JS_ASSERT(!masm_.hasEnteredExitFrame()); |
| |
| // Patch everything that needs an absolute address: |
| |
| // Entry points |
| for (unsigned i = 0; i < module_->numExportedFunctions(); i++) |
| module_->exportedFunction(i).patch(code); |
| |
| // Exit points |
| for (unsigned i = 0; i < module_->numExits(); i++) { |
| module_->exit(i).patch(code); |
| module_->exitIndexToGlobalDatum(i).exit = module_->exit(i).interpCode(); |
| module_->exitIndexToGlobalDatum(i).fun = NULL; |
| } |
| module_->setOperationCallbackExit(code + masm_.actualOffset(operationCallbackLabel_.offset())); |
| |
| // Function-pointer table entries |
| unsigned elemIndex = 0; |
| for (unsigned i = 0; i < funcPtrTables_.length(); i++) { |
| FuncPtrTable &table = funcPtrTables_[i]; |
| JS_ASSERT(elemIndex == table.baseIndex()); |
| for (unsigned j = 0; j < table.numElems(); j++) { |
| uint8_t *funcPtr = code + masm_.actualOffset(table.elem(j).codeLabel()->offset()); |
| module_->funcPtrIndexToGlobalDatum(elemIndex++) = funcPtr; |
| } |
| JS_ASSERT(elemIndex == table.baseIndex() + table.numElems()); |
| } |
| JS_ASSERT(elemIndex == module_->numFuncPtrTableElems()); |
| |
| // Global accesses in function bodies |
| #if defined(JS_CPU_ARM) |
| JS_ASSERT(globalAccesses_.length() == 0); |
| // The AsmJSHeapAccess offsets need to be updated to reflect the |
| // "actualOffset" (an ARM distinction). |
| module_->convertBoundsChecksToActualOffset(masm_); |
| |
| #elif defined(JS_CPU_X86) || defined(JS_CPU_X64) |
| |
| for (unsigned i = 0; i < globalAccesses_.length(); i++) { |
| AsmJSGlobalAccess access = globalAccesses_[i]; |
| masm_.patchAsmJSGlobalAccess(access.offset, code, codeBytes, access.globalDataOffset); |
| } |
| #endif |
| // The AsmJSHeapAccess offsets need to be updated to reflect the |
| // "actualOffset" (an ARM distinction). |
| for (unsigned i = 0; i < module_->numHeapAccesses(); i++) { |
| AsmJSHeapAccess &access = module_->heapAccess(i); |
| access.updateOffset(masm_.actualOffset(access.offset())); |
| } |
| |
| *module = module_.forget(); |
| return true; |
| } |
| }; |
| |
| /*****************************************************************************/ |
| |
| // 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. Also see ModuleCompiler comment. |
| class FunctionCompiler |
| { |
| public: |
| struct Local |
| { |
| enum Which { Var, Arg } which; |
| VarType type; |
| unsigned slot; |
| Value initialValue; |
| |
| Local(VarType t, unsigned slot) |
| : which(Arg), type(t), slot(slot), initialValue(MagicValue(JS_GENERIC_MAGIC)) {} |
| Local(VarType t, unsigned slot, const Value &init) |
| : which(Var), type(t), slot(slot), initialValue(init) {} |
| }; |
| typedef HashMap<PropertyName*, Local> LocalMap; |
| |
| private: |
| typedef HashMap<PropertyName*, BlockVector> LabeledBlockMap; |
| typedef HashMap<ParseNode*, BlockVector> UnlabeledBlockMap; |
| typedef Vector<ParseNode*, 4> NodeStack; |
| |
| ModuleCompiler & m_; |
| ModuleCompiler::Func & func_; |
| LocalMap locals_; |
| |
| MIRGenerator * mirGen_; |
| AutoFlushCache autoFlushCache_; |
| |
| MBasicBlock * curBlock_; |
| NodeStack loopStack_; |
| NodeStack breakableStack_; |
| UnlabeledBlockMap unlabeledBreaks_; |
| UnlabeledBlockMap unlabeledContinues_; |
| LabeledBlockMap labeledBreaks_; |
| LabeledBlockMap labeledContinues_; |
| |
| public: |
| FunctionCompiler(ModuleCompiler &m, ModuleCompiler::Func &func, |
| MoveRef<LocalMap> locals, MIRGenerator *mirGen) |
| : m_(m), |
| func_(func), |
| locals_(locals), |
| mirGen_(mirGen), |
| autoFlushCache_("asm.js"), |
| curBlock_(NULL), |
| loopStack_(m.cx()), |
| breakableStack_(m.cx()), |
| unlabeledBreaks_(m.cx()), |
| unlabeledContinues_(m.cx()), |
| labeledBreaks_(m.cx()), |
| labeledContinues_(m.cx()) |
| {} |
| |
| bool init() |
| { |
| if (!unlabeledBreaks_.init() || |
| !unlabeledContinues_.init() || |
| !labeledBreaks_.init() || |
| !labeledContinues_.init()) |
| { |
| return false; |
| } |
| |
| if (!newBlock(/* pred = */ NULL, &curBlock_)) |
| return false; |
| |
| curBlock_->add(MAsmJSCheckOverRecursed::New(&m_.stackOverflowLabel())); |
| |
| for (ABIArgIter i(func_.argMIRTypes()); !i.done(); i++) { |
| MAsmJSParameter *ins = MAsmJSParameter::New(*i, i.mirType()); |
| curBlock_->add(ins); |
| curBlock_->initSlot(info().localSlot(i.index()), ins); |
| } |
| |
| for (LocalMap::Range r = locals_.all(); !r.empty(); r.popFront()) { |
| const Local &local = r.front().value; |
| if (local.which == Local::Var) { |
| MConstant *ins = MConstant::New(local.initialValue); |
| curBlock_->add(ins); |
| curBlock_->initSlot(info().localSlot(local.slot), ins); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool fail(ParseNode *pn, const char *str) |
| { |
| return m_.fail(pn, str); |
| } |
| |
| bool failf(ParseNode *pn, const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| m_.failfVA(pn, fmt, ap); |
| va_end(ap); |
| return false; |
| } |
| |
| bool failName(ParseNode *pn, const char *fmt, PropertyName *name) |
| { |
| return m_.failName(pn, fmt, name); |
| } |
| |
| ~FunctionCompiler() |
| { |
| if (!m().hasError() && !cx()->isExceptionPending()) { |
| JS_ASSERT(loopStack_.empty()); |
| JS_ASSERT(unlabeledBreaks_.empty()); |
| JS_ASSERT(unlabeledContinues_.empty()); |
| JS_ASSERT(labeledBreaks_.empty()); |
| JS_ASSERT(labeledContinues_.empty()); |
| JS_ASSERT(curBlock_ == NULL); |
| } |
| } |
| |
| /*************************************************** Read-only interface */ |
| |
| JSContext * cx() const { return m_.cx(); } |
| ModuleCompiler & m() const { return m_; } |
| const AsmJSModule & module() const { return m_.module(); } |
| ModuleCompiler::Func & func() const { return func_; } |
| MIRGenerator & mirGen() { return *mirGen_; } |
| MIRGraph & mirGraph() { return mirGen_->graph(); } |
| CompileInfo & info() { return mirGen_->info(); } |
| |
| const Local *lookupLocal(PropertyName *name) const |
| { |
| if (LocalMap::Ptr p = locals_.lookup(name)) |
| return &p->value; |
| return NULL; |
| } |
| |
| MDefinition *getLocalDef(const Local &local) |
| { |
| if (!curBlock_) |
| return NULL; |
| return curBlock_->getSlot(info().localSlot(local.slot)); |
| } |
| |
| const ModuleCompiler::Func *lookupFunction(PropertyName *name) const |
| { |
| if (locals_.has(name)) |
| return NULL; |
| if (const ModuleCompiler::Func *func = m_.lookupFunction(name)) |
| return func; |
| return NULL; |
| } |
| |
| const ModuleCompiler::Global *lookupGlobal(PropertyName *name) const |
| { |
| if (locals_.has(name)) |
| return NULL; |
| return m_.lookupGlobal(name); |
| } |
| |
| /************************************************* Expression generation */ |
| |
| MDefinition *constant(const Value &v) |
| { |
| if (!curBlock_) |
| return NULL; |
| JS_ASSERT(v.isNumber()); |
| MConstant *constant = MConstant::New(v); |
| curBlock_->add(constant); |
| return constant; |
| } |
| |
| template <class T> |
| MDefinition *unary(MDefinition *op) |
| { |
| if (!curBlock_) |
| return NULL; |
| T *ins = T::NewAsmJS(op); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| template <class T> |
| MDefinition *unary(MDefinition *op, MIRType type) |
| { |
| if (!curBlock_) |
| return NULL; |
| T *ins = T::NewAsmJS(op, type); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| template <class T> |
| MDefinition *binary(MDefinition *lhs, MDefinition *rhs) |
| { |
| if (!curBlock_) |
| return NULL; |
| T *ins = T::New(lhs, rhs); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| template <class T> |
| MDefinition *binary(MDefinition *lhs, MDefinition *rhs, MIRType type) |
| { |
| if (!curBlock_) |
| return NULL; |
| T *ins = T::NewAsmJS(lhs, rhs, type); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| MDefinition *mul(MDefinition *lhs, MDefinition *rhs, MIRType type, MMul::Mode mode) |
| { |
| if (!curBlock_) |
| return NULL; |
| MMul *ins = MMul::New(lhs, rhs, type, mode); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| template <class T> |
| MDefinition *bitwise(MDefinition *lhs, MDefinition *rhs) |
| { |
| if (!curBlock_) |
| return NULL; |
| T *ins = T::NewAsmJS(lhs, rhs); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| template <class T> |
| MDefinition *bitwise(MDefinition *op) |
| { |
| if (!curBlock_) |
| return NULL; |
| T *ins = T::NewAsmJS(op); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| MDefinition *compare(MDefinition *lhs, MDefinition *rhs, JSOp op, MCompare::CompareType type) |
| { |
| if (!curBlock_) |
| return NULL; |
| MCompare *ins = MCompare::NewAsmJS(lhs, rhs, op, type); |
| curBlock_->add(ins); |
| return ins; |
| } |
| |
| void assign(const Local &local, MDefinition *def) |
| { |
| if (!curBlock_) |
| return; |
| curBlock_->setSlot(info().localSlot(local.slot), def); |
| } |
| |
| MDefinition *loadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr) |
| { |
| if (!curBlock_) |
| return NULL; |
| MAsmJSLoadHeap *load = MAsmJSLoadHeap::New(vt, ptr); |
| curBlock_->add(load); |
| return load; |
| } |
| |
| void storeHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v) |
| { |
| if (!curBlock_) |
| return; |
| curBlock_->add(MAsmJSStoreHeap::New(vt, ptr, v)); |
| } |
| |
| MDefinition *loadGlobalVar(const ModuleCompiler::Global &global) |
| { |
| if (!curBlock_) |
| return NULL; |
| MIRType type = global.varType().toMIRType(); |
| unsigned globalDataOffset = module().globalVarIndexToGlobalDataOffset(global.varIndex()); |
| MAsmJSLoadGlobalVar *load = MAsmJSLoadGlobalVar::New(type, globalDataOffset); |
| curBlock_->add(load); |
| return load; |
| } |
| |
| void storeGlobalVar(const ModuleCompiler::Global &global, MDefinition *v) |
| { |
| if (!curBlock_) |
| return; |
| unsigned globalDataOffset = module().globalVarIndexToGlobalDataOffset(global.varIndex()); |
| curBlock_->add(MAsmJSStoreGlobalVar::New(globalDataOffset, v)); |
| } |
| |
| /***************************************************************** 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 Args |
| { |
| ABIArgGenerator abi_; |
| uint32_t prevMaxStackBytes_; |
| uint32_t maxChildStackBytes_; |
| uint32_t spIncrement_; |
| Vector<Type, 8> types_; |
| MAsmJSCall::Args regArgs_; |
| Vector<MAsmJSPassStackArg*> stackArgs_; |
| bool childClobbers_; |
| |
| friend class FunctionCompiler; |
| |
| public: |
| Args(FunctionCompiler &f) |
| : prevMaxStackBytes_(0), |
| maxChildStackBytes_(0), |
| spIncrement_(0), |
| types_(f.cx()), |
| regArgs_(f.cx()), |
| stackArgs_(f.cx()), |
| childClobbers_(false) |
| {} |
| unsigned length() const { |
| return types_.length(); |
| } |
| Type type(unsigned i) const { |
| return types_[i]; |
| } |
| }; |
| |
| void startCallArgs(Args *args) |
| { |
| if (!curBlock_) |
| return; |
| args->prevMaxStackBytes_ = mirGen().resetAsmJSMaxStackArgBytes(); |
| } |
| |
| bool passArg(MDefinition *argDef, Type type, Args *args) |
| { |
| if (!args->types_.append(type)) |
| return false; |
| |
| if (!curBlock_) |
| return true; |
| |
| uint32_t childStackBytes = mirGen().resetAsmJSMaxStackArgBytes(); |
| args->maxChildStackBytes_ = Max(args->maxChildStackBytes_, childStackBytes); |
| if (childStackBytes > 0 && !args->stackArgs_.empty()) |
| args->childClobbers_ = true; |
| |
| ABIArg arg = args->abi_.next(type.toMIRType()); |
| if (arg.kind() == ABIArg::Stack) { |
| MAsmJSPassStackArg *mir = MAsmJSPassStackArg::New(arg.offsetFromArgBase(), argDef); |
| curBlock_->add(mir); |
| if (!args->stackArgs_.append(mir)) |
| return false; |
| } else { |
| if (!args->regArgs_.append(MAsmJSCall::Arg(arg.reg(), argDef))) |
| return false; |
| } |
| return true; |
| } |
| |
| void finishCallArgs(Args *args) |
| { |
| if (!curBlock_) |
| return; |
| uint32_t parentStackBytes = args->abi_.stackBytesConsumedSoFar(); |
| uint32_t newStackBytes; |
| if (args->childClobbers_) { |
| args->spIncrement_ = AlignBytes(args->maxChildStackBytes_, StackAlignment); |
| for (unsigned i = 0; i < args->stackArgs_.length(); i++) |
| args->stackArgs_[i]->incrementOffset(args->spIncrement_); |
| newStackBytes = Max(args->prevMaxStackBytes_, |
| args->spIncrement_ + parentStackBytes); |
| } else { |
| args->spIncrement_ = 0; |
| newStackBytes = Max(args->prevMaxStackBytes_, |
| Max(args->maxChildStackBytes_, parentStackBytes)); |
| } |
| mirGen_->setAsmJSMaxStackArgBytes(newStackBytes); |
| } |
| |
| private: |
| bool call(MAsmJSCall::Callee callee, const Args &args, MIRType returnType, MDefinition **def) |
| { |
| if (!curBlock_) { |
| *def = NULL; |
| return true; |
| } |
| MAsmJSCall *ins = MAsmJSCall::New(callee, args.regArgs_, returnType, args.spIncrement_); |
| if (!ins) |
| return false; |
| curBlock_->add(ins); |
| *def = ins; |
| return true; |
| } |
| |
| public: |
| bool internalCall(const ModuleCompiler::Func &func, const Args &args, MDefinition **def) |
| { |
| MIRType returnType = func.returnType().toMIRType(); |
| return call(MAsmJSCall::Callee(func.codeLabel()), args, returnType, def); |
| } |
| |
| bool funcPtrCall(const ModuleCompiler::FuncPtrTable &funcPtrTable, MDefinition *index, |
| const Args &args, MDefinition **def) |
| { |
| if (!curBlock_) { |
| *def = NULL; |
| return true; |
| } |
| |
| MConstant *mask = MConstant::New(Int32Value(funcPtrTable.mask())); |
| curBlock_->add(mask); |
| MBitAnd *maskedIndex = MBitAnd::NewAsmJS(index, mask); |
| curBlock_->add(maskedIndex); |
| unsigned globalDataOffset = module().funcPtrIndexToGlobalDataOffset(funcPtrTable.baseIndex()); |
| MAsmJSLoadFuncPtr *ptrFun = MAsmJSLoadFuncPtr::New(globalDataOffset, maskedIndex); |
| curBlock_->add(ptrFun); |
| |
| MIRType returnType = funcPtrTable.sig().returnType().toMIRType(); |
| return call(MAsmJSCall::Callee(ptrFun), args, returnType, def); |
| } |
| |
| bool ffiCall(unsigned exitIndex, const Args &args, MIRType returnType, MDefinition **def) |
| { |
| if (!curBlock_) { |
| *def = NULL; |
| return true; |
| } |
| |
| JS_STATIC_ASSERT(offsetof(AsmJSModule::ExitDatum, exit) == 0); |
| unsigned globalDataOffset = module().exitIndexToGlobalDataOffset(exitIndex); |
| |
| MAsmJSLoadFFIFunc *ptrFun = MAsmJSLoadFFIFunc::New(globalDataOffset); |
| curBlock_->add(ptrFun); |
| |
| return call(MAsmJSCall::Callee(ptrFun), args, returnType, def); |
| } |
| |
| bool builtinCall(void *builtin, const Args &args, MIRType returnType, MDefinition **def) |
| { |
| return call(MAsmJSCall::Callee(builtin), args, returnType, def); |
| } |
| |
| /*********************************************** Control flow generation */ |
| |
| void returnExpr(MDefinition *expr) |
| { |
| if (!curBlock_) |
| return; |
| MAsmJSReturn *ins = MAsmJSReturn::New(expr); |
| curBlock_->end(ins); |
| curBlock_ = NULL; |
| } |
| |
| void returnVoid() |
| { |
| if (!curBlock_) |
| return; |
| MAsmJSVoidReturn *ins = MAsmJSVoidReturn::New(); |
| curBlock_->end(ins); |
| curBlock_ = NULL; |
| } |
| |
| bool branchAndStartThen(MDefinition *cond, MBasicBlock **thenBlock, MBasicBlock **elseBlock) |
| { |
| if (!curBlock_) { |
| *thenBlock = NULL; |
| *elseBlock = NULL; |
| return true; |
| } |
| if (!newBlock(curBlock_, thenBlock) || !newBlock(curBlock_, elseBlock)) |
| return false; |
| curBlock_->end(MTest::New(cond, *thenBlock, *elseBlock)); |
| curBlock_ = *thenBlock; |
| return true; |
| } |
| |
| bool appendThenBlock(BlockVector *thenBlocks) { |
| if (!curBlock_) |
| return true; |
| return thenBlocks->append(curBlock_); |
| } |
| |
| void joinIf(const BlockVector &thenBlocks, MBasicBlock *joinBlock) |
| { |
| if (!joinBlock) |
| return; |
| JS_ASSERT_IF(curBlock_, thenBlocks.back() == curBlock_); |
| for (size_t i = 0; i < thenBlocks.length(); i++) { |
| thenBlocks[i]->end(MGoto::New(joinBlock)); |
| joinBlock->addPredecessor(thenBlocks[i]); |
| } |
| curBlock_ = joinBlock; |
| mirGraph().moveBlockToEnd(curBlock_); |
| } |
| |
| void switchToElse(MBasicBlock *elseBlock) |
| { |
| if (!elseBlock) |
| return; |
| curBlock_ = elseBlock; |
| mirGraph().moveBlockToEnd(curBlock_); |
| } |
| |
| bool joinIfElse(const BlockVector &thenBlocks) |
| { |
| if (!curBlock_ && thenBlocks.empty()) |
| return true; |
| MBasicBlock *pred = curBlock_ ? curBlock_ : thenBlocks[0]; |
| MBasicBlock *join; |
| if (!newBlock(pred, &join)) |
| return false; |
| if (curBlock_) |
| curBlock_->end(MGoto::New(join)); |
| for (size_t i = 0; i < thenBlocks.length(); i++) { |
| thenBlocks[i]->end(MGoto::New(join)); |
| if (pred == curBlock_ || i > 0) |
| join->addPredecessor(thenBlocks[i]); |
| } |
| curBlock_ = join; |
| return true; |
| } |
| |
| void pushPhiInput(MDefinition *def) |
| { |
| if (!curBlock_) |
| return; |
| JS_ASSERT(curBlock_->stackDepth() == info().firstStackSlot()); |
| curBlock_->push(def); |
| } |
| |
| MDefinition *popPhiOutput() |
| { |
| if (!curBlock_) |
| return NULL; |
| JS_ASSERT(curBlock_->stackDepth() == info().firstStackSlot() + 1); |
| return curBlock_->pop(); |
| } |
| |
| bool startPendingLoop(ParseNode *pn, MBasicBlock **loopEntry) |
| { |
| if (!loopStack_.append(pn) || !breakableStack_.append(pn)) |
| return false; |
| JS_ASSERT_IF(curBlock_, curBlock_->loopDepth() == loopStack_.length() - 1); |
| if (!curBlock_) { |
| *loopEntry = NULL; |
| return true; |
| } |
| *loopEntry = MBasicBlock::NewPendingLoopHeader(mirGraph(), info(), curBlock_, NULL); |
| if (!*loopEntry) |
| return false; |
| mirGraph().addBlock(*loopEntry); |
| (*loopEntry)->setLoopDepth(loopStack_.length()); |
| curBlock_->end(MGoto::New(*loopEntry)); |
| curBlock_ = *loopEntry; |
| return true; |
| } |
| |
| bool branchAndStartLoopBody(MDefinition *cond, MBasicBlock **afterLoop) |
| { |
| if (!curBlock_) { |
| *afterLoop = NULL; |
| return true; |
| } |
| JS_ASSERT(curBlock_->loopDepth() > 0); |
| MBasicBlock *body; |
| if (!newBlock(curBlock_, &body)) |
| return false; |
| if (cond->isConstant() && ToBoolean(cond->toConstant()->value())) { |
| *afterLoop = NULL; |
| curBlock_->end(MGoto::New(body)); |
| } else { |
| if (!newBlockWithDepth(curBlock_, curBlock_->loopDepth() - 1, afterLoop)) |
| return false; |
| curBlock_->end(MTest::New(cond, body, *afterLoop)); |
| } |
| curBlock_ = body; |
| return true; |
| } |
| |
| private: |
| ParseNode *popLoop() |
| { |
| ParseNode *pn = loopStack_.back(); |
| JS_ASSERT(!unlabeledContinues_.has(pn)); |
| loopStack_.popBack(); |
| breakableStack_.popBack(); |
| return pn; |
| } |
| |
| public: |
| bool closeLoop(MBasicBlock *loopEntry, MBasicBlock *afterLoop) |
| { |
| ParseNode *pn = popLoop(); |
| if (!loopEntry) { |
| JS_ASSERT(!afterLoop); |
| JS_ASSERT(!curBlock_); |
| JS_ASSERT(!unlabeledBreaks_.has(pn)); |
| return true; |
| } |
| JS_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1); |
| JS_ASSERT_IF(afterLoop, afterLoop->loopDepth() == loopStack_.length()); |
| if (curBlock_) { |
| JS_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1); |
| curBlock_->end(MGoto::New(loopEntry)); |
| loopEntry->setBackedge(curBlock_); |
| } |
| curBlock_ = afterLoop; |
| if (curBlock_) |
| mirGraph().moveBlockToEnd(curBlock_); |
| return bindUnlabeledBreaks(pn); |
| } |
| |
| bool branchAndCloseDoWhileLoop(MDefinition *cond, MBasicBlock *loopEntry) |
| { |
| ParseNode *pn = popLoop(); |
| if (!loopEntry) { |
| JS_ASSERT(!curBlock_); |
| JS_ASSERT(!unlabeledBreaks_.has(pn)); |
| return true; |
| } |
| JS_ASSERT(loopEntry->loopDepth() == loopStack_.length() + 1); |
| if (curBlock_) { |
| JS_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1); |
| if (cond->isConstant()) { |
| if (ToBoolean(cond->toConstant()->value())) { |
| curBlock_->end(MGoto::New(loopEntry)); |
| loopEntry->setBackedge(curBlock_); |
| curBlock_ = NULL; |
| } else { |
| MBasicBlock *afterLoop; |
| if (!newBlock(curBlock_, &afterLoop)) |
| return false; |
| curBlock_->end(MGoto::New(afterLoop)); |
| curBlock_ = afterLoop; |
| } |
| } else { |
| MBasicBlock *afterLoop; |
| if (!newBlock(curBlock_, &afterLoop)) |
| return false; |
| curBlock_->end(MTest::New(cond, loopEntry, afterLoop)); |
| loopEntry->setBackedge(curBlock_); |
| curBlock_ = afterLoop; |
| } |
| } |
| return bindUnlabeledBreaks(pn); |
| } |
| |
| bool bindContinues(ParseNode *pn, const LabelVector *maybeLabels) |
| { |
| bool createdJoinBlock = false; |
| if (UnlabeledBlockMap::Ptr p = unlabeledContinues_.lookup(pn)) { |
| 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(PropertyName *maybeLabel) { |
| if (maybeLabel) |
| return addBreakOrContinue(maybeLabel, &labeledBreaks_); |
| return addBreakOrContinue(breakableStack_.back(), &unlabeledBreaks_); |
| } |
| |
| bool addContinue(PropertyName *maybeLabel) { |
| if (maybeLabel) |
| return addBreakOrContinue(maybeLabel, &labeledContinues_); |
| return addBreakOrContinue(loopStack_.back(), &unlabeledContinues_); |
| } |
| |
| bool startSwitch(ParseNode *pn, MDefinition *expr, int32_t low, int32_t high, |
| MBasicBlock **switchBlock) |
| { |
| if (!breakableStack_.append(pn)) |
| return false; |
| if (!curBlock_) { |
| *switchBlock = NULL; |
| return true; |
| } |
| curBlock_->end(MTableSwitch::New(expr, low, high)); |
| *switchBlock = curBlock_; |
| curBlock_ = NULL; |
| return true; |
| } |
| |
| bool startSwitchCase(MBasicBlock *switchBlock, MBasicBlock **next) |
| { |
| if (!switchBlock) { |
| *next = NULL; |
| return true; |
| } |
| if (!newBlock(switchBlock, next)) |
| return false; |
| if (curBlock_) { |
| curBlock_->end(MGoto::New(*next)); |
| (*next)->addPredecessor(curBlock_); |
| } |
| curBlock_ = *next; |
| return true; |
| } |
| |
| bool startSwitchDefault(MBasicBlock *switchBlock, BlockVector *cases, MBasicBlock **defaultBlock) |
| { |
| if (!startSwitchCase(switchBlock, defaultBlock)) |
| return false; |
| if (!*defaultBlock) |
| return true; |
| for (unsigned i = 0; i < cases->length(); i++) { |
| if (!(*cases)[i]) { |
| MBasicBlock *bb; |
| if (!newBlock(switchBlock, &bb)) |
| return false; |
| bb->end(MGoto::New(*defaultBlock)); |
| (*defaultBlock)->addPredecessor(bb); |
| (*cases)[i] = bb; |
| } |
| } |
| mirGraph().moveBlockToEnd(*defaultBlock); |
| return true; |
| } |
| |
| bool joinSwitch(MBasicBlock *switchBlock, const BlockVector &cases, MBasicBlock *defaultBlock) |
| { |
| ParseNode *pn = breakableStack_.popCopy(); |
| if (!switchBlock) |
| return true; |
| MTableSwitch *mir = switchBlock->lastIns()->toTableSwitch(); |
| mir->addDefault(defaultBlock); |
| for (unsigned i = 0; i < cases.length(); i++) |
| mir->addCase(cases[i]); |
| if (curBlock_) { |
| MBasicBlock *next; |
| if (!newBlock(curBlock_, &next)) |
| return false; |
| curBlock_->end(MGoto::New(next)); |
| curBlock_ = next; |
| } |
| return bindUnlabeledBreaks(pn); |
| } |
| |
| /*************************************************************************/ |
| private: |
| bool newBlockWithDepth(MBasicBlock *pred, unsigned loopDepth, MBasicBlock **block) |
| { |
| *block = MBasicBlock::New(mirGraph(), info(), pred, /* pc = */ NULL, 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(curBlock_)); |
| curBlock_->addPredecessor(pred); |
| } else { |
| MBasicBlock *next; |
| if (!newBlock(pred, &next)) |
| return false; |
| pred->end(MGoto::New(next)); |
| if (curBlock_) { |
| curBlock_->end(MGoto::New(next)); |
| next->addPredecessor(curBlock_); |
| } |
| curBlock_ = next; |
| *createdJoinBlock = true; |
| } |
| JS_ASSERT(curBlock_->begin() == curBlock_->end()); |
| } |
| 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); |
| } |
| } |
| return true; |
| } |
| |
| template <class Key, class Map> |
| bool addBreakOrContinue(Key key, Map *map) |
| { |
| if (!curBlock_) |
| return true; |
| typename Map::AddPtr p = map->lookupForAdd(key); |
| if (!p) { |
| BlockVector empty(m().cx()); |
| if (!map->add(p, key, Move(empty))) |
| return false; |
| } |
| if (!p->value.append(curBlock_)) |
| return false; |
| curBlock_ = NULL; |
| return true; |
| } |
| |
| bool bindUnlabeledBreaks(ParseNode *pn) |
| { |
| bool createdJoinBlock = false; |
| if (UnlabeledBlockMap::Ptr p = unlabeledBreaks_.lookup(pn)) { |
| if (!bindBreaksOrContinues(&p->value, &createdJoinBlock)) |
| return false; |
| unlabeledBreaks_.remove(p); |
| } |
| return true; |
| } |
| }; |
| |
| /*****************************************************************************/ |
| // An AsmJSModule contains the persistent results of asm.js module compilation, |
| // viz., the jit code and dynamic link information. |
| // |
| // An AsmJSModule object is created at the end of module compilation and |
| // subsequently owned by an AsmJSModuleClass JSObject. |
| |
| static void AsmJSModuleObject_finalize(FreeOp *fop, JSObject *obj); |
| static void AsmJSModuleObject_trace(JSTracer *trc, JSObject *obj); |
| |
| static const unsigned ASM_CODE_RESERVED_SLOT = 0; |
| static const unsigned ASM_CODE_NUM_RESERVED_SLOTS = 1; |
| |
| static Class AsmJSModuleClass = { |
| "AsmJSModuleObject", |
| JSCLASS_IS_ANONYMOUS | JSCLASS_IMPLEMENTS_BARRIERS | |
| JSCLASS_HAS_RESERVED_SLOTS(ASM_CODE_NUM_RESERVED_SLOTS), |
| JS_PropertyStub, /* addProperty */ |
| JS_DeletePropertyStub, /* delProperty */ |
| JS_PropertyStub, /* getProperty */ |
| JS_StrictPropertyStub, /* setProperty */ |
| JS_EnumerateStub, |
| JS_ResolveStub, |
| NULL, /* convert */ |
| AsmJSModuleObject_finalize, |
| NULL, /* checkAccess */ |
| NULL, /* call */ |
| NULL, /* hasInstance */ |
| NULL, /* construct */ |
| AsmJSModuleObject_trace |
| }; |
| |
| AsmJSModule & |
| js::AsmJSModuleObjectToModule(JSObject *obj) |
| { |
| JS_ASSERT(obj->getClass() == &AsmJSModuleClass); |
| return *(AsmJSModule *)obj->getReservedSlot(ASM_CODE_RESERVED_SLOT).toPrivate(); |
| } |
| |
| bool |
| js::IsAsmJSModuleObject(JSObject *obj) |
| { |
| return obj->getClass() == &AsmJSModuleClass; |
| } |
| |
| static const unsigned ASM_MODULE_FUNCTION_MODULE_OBJECT_SLOT = 0; |
| |
| JSObject & |
| js::AsmJSModuleObject(JSFunction *moduleFun) |
| { |
| return moduleFun->getExtendedSlot(ASM_MODULE_FUNCTION_MODULE_OBJECT_SLOT).toObject(); |
| } |
| |
| void |
| js::SetAsmJSModuleObject(JSFunction *moduleFun, JSObject *moduleObj) |
| { |
| moduleFun->setExtendedSlot(ASM_MODULE_FUNCTION_MODULE_OBJECT_SLOT, OBJECT_TO_JSVAL(moduleObj)); |
| } |
| |
| static void |
| AsmJSModuleObject_finalize(FreeOp *fop, JSObject *obj) |
| { |
| fop->delete_(&AsmJSModuleObjectToModule(obj)); |
| } |
| |
| static void |
| AsmJSModuleObject_trace(JSTracer *trc, JSObject *obj) |
| { |
| AsmJSModuleObjectToModule(obj).trace(trc); |
| } |
| |
| static JSObject * |
| NewAsmJSModuleObject(JSContext *cx, ScopedJSDeletePtr<AsmJSModule> *module) |
| { |
| JSObject *obj = NewObjectWithGivenProto(cx, &AsmJSModuleClass, NULL, NULL); |
| if (!obj) |
| return NULL; |
| |
| obj->setReservedSlot(ASM_CODE_RESERVED_SLOT, PrivateValue(module->forget())); |
| return obj; |
| } |
| |
| /*****************************************************************************/ |
| // asm.js type-checking and code-generation algorithm |
| |
| static bool |
| CheckIdentifier(ModuleCompiler &m, PropertyName *name, ParseNode *nameNode) |
| { |
| if (name == m.cx()->names().arguments || name == m.cx()->names().eval) |
| return m.failName(nameNode, "'%s' is not an allowed identifier", name); |
| return true; |
| } |
| |
| static bool |
| CheckModuleLevelName(ModuleCompiler &m, PropertyName *name, ParseNode *nameNode) |
| { |
| if (!CheckIdentifier(m, name, nameNode)) |
| return false; |
| |
| if (name == m.moduleFunctionName() || |
| name == m.module().globalArgumentName() || |
| name == m.module().importArgumentName() || |
| name == m.module().bufferArgumentName() || |
| m.lookupGlobal(name)) |
| { |
| return m.failName(nameNode, "duplicate name '%s' not allowed", name); |
| } |
| |
| return true; |
| } |
| |
| static bool |
| CheckFunctionHead(ModuleCompiler &m, ParseNode *fn, ParseNode **stmtIter) |
| { |
| if (FunctionObject(fn)->hasRest()) |
| return m.fail(fn, "rest args not allowed"); |
| if (!FunctionHasStatementList(fn)) |
| return m.fail(fn, "expression closures not allowed"); |
| |
| *stmtIter = ListHead(FunctionStatementList(fn)); |
| return true; |
| } |
| |
| static bool |
| CheckArgument(ModuleCompiler &m, ParseNode *arg, PropertyName **name) |
| { |
| if (!IsDefinition(arg)) |
| return m.fail(arg, "duplicate argument name not allowed"); |
| |
| if (MaybeDefinitionInitializer(arg)) |
| return m.fail(arg, "default arguments not allowed"); |
| |
| if (!CheckIdentifier(m, arg->name(), arg)) |
| return false; |
| |
| *name = arg->name(); |
| return true; |
| } |
| |
| static bool |
| CheckModuleArgument(ModuleCompiler &m, ParseNode *arg, PropertyName **name) |
| { |
| if (!CheckArgument(m, arg, name)) |
| return false; |
| |
| if (!CheckModuleLevelName(m, *name, arg)) |
| return false; |
| |
| return true; |
| } |
| |
| static bool |
| CheckModuleArguments(ModuleCompiler &m, ParseNode *fn) |
| { |
| unsigned numFormals; |
| ParseNode *arg1 = FunctionArgsList(fn, &numFormals); |
| ParseNode *arg2 = arg1 ? NextNode(arg1) : NULL; |
| ParseNode *arg3 = arg2 ? NextNode(arg2) : NULL; |
| |
| if (numFormals > 3) |
| return m.fail(fn, "asm.js modules takes at most 3 argument"); |
| |
| PropertyName *arg1Name = NULL; |
| if (numFormals >= 1 && !CheckModuleArgument(m, arg1, &arg1Name)) |
| return false; |
| m.initGlobalArgumentName(arg1Name); |
| |
| PropertyName *arg2Name = NULL; |
| if (numFormals >= 2 && !CheckModuleArgument(m, arg2, &arg2Name)) |
| return false; |
| m.initImportArgumentName(arg2Name); |
| |
| PropertyName *arg3Name = NULL; |
| if (numFormals >= 3 && !CheckModuleArgument(m, arg3, &arg3Name)) |
| return false; |
| m.initBufferArgumentName(arg3Name); |
| |
| return true; |
| } |
| |
| static bool |
| SkipUseAsmDirective(ModuleCompiler &m, ParseNode **stmtIter) |
| { |
| ParseNode *firstStatement = *stmtIter; |
| |
| if (!IsExpressionStatement(firstStatement)) |
| return m.fail(firstStatement, "unsupported statement before 'use asm' directive"); |
| |
| ParseNode *expr = ExpressionStatementExpr(firstStatement); |
| if (!expr || !expr->isKind(PNK_STRING)) |
| return m.fail(firstStatement, "unsupported statement before 'use asm' directive"); |
| |
| if (StringAtom(expr) != m.cx()->names().useAsm) |
| return m.fail(firstStatement, "\"use asm\" precludes other directives"); |
| |
| *stmtIter = NextNonEmptyStatement(firstStatement); |
| if (*stmtIter |
| && IsExpressionStatement(*stmtIter) |
| && ExpressionStatementExpr(*stmtIter)->isKind(PNK_STRING)) |
| { |
| return m.fail(*stmtIter, "\"use asm\" precludes other directives"); |
| } |
| |
| return true; |
| } |
| |
| static bool |
| CheckGlobalVariableInitConstant(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode) |
| { |
| NumLit literal = ExtractNumericLiteral(initNode); |
| VarType type; |
| switch (literal.which()) { |
| case NumLit::Fixnum: |
| case NumLit::NegativeInt: |
| case NumLit::BigUnsigned: |
| type = VarType::Int; |
| break; |
| case NumLit::Double: |
| type = VarType::Double; |
| break; |
| case NumLit::OutOfRangeInt: |
| return m.fail(initNode, "global initializer is out of representable integer range"); |
| } |
| return m.addGlobalVarInitConstant(varName, type, literal.value()); |
| } |
| |
| static bool |
| CheckTypeAnnotation(ModuleCompiler &m, ParseNode *coercionNode, AsmJSCoercion *coercion, |
| ParseNode **coercedExpr = NULL) |
| { |
| switch (coercionNode->getKind()) { |
| case PNK_BITOR: { |
| ParseNode *rhs = BinaryRight(coercionNode); |
| |
| if (!IsNumericLiteral(rhs)) |
| return m.fail(rhs, "must use |0 for argument/return coercion"); |
| |
| NumLit rhsLiteral = ExtractNumericLiteral(rhs); |
| if (rhsLiteral.which() != NumLit::Fixnum || rhsLiteral.toInt32() != 0) |
| return m.fail(rhs, "must use |0 for argument/return coercion"); |
| |
| *coercion = AsmJS_ToInt32; |
| if (coercedExpr) |
| *coercedExpr = BinaryLeft(coercionNode); |
| return true; |
| } |
| case PNK_POS: { |
| *coercion = AsmJS_ToNumber; |
| if (coercedExpr) |
| *coercedExpr = UnaryKid(coercionNode); |
| return true; |
| } |
| default:; |
| } |
| |
| return m.fail(coercionNode, "in coercion expression, the expression must be of the form +x or x|0"); |
| } |
| |
| static bool |
| CheckGlobalVariableInitImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode) |
| { |
| AsmJSCoercion coercion; |
| ParseNode *coercedExpr; |
| if (!CheckTypeAnnotation(m, initNode, &coercion, &coercedExpr)) |
| return false; |
| |
| if (!coercedExpr->isKind(PNK_DOT)) |
| return m.failName(coercedExpr, "invalid import expression for global '%s'", varName); |
| |
| ParseNode *base = DotBase(coercedExpr); |
| PropertyName *field = DotMember(coercedExpr); |
| |
| PropertyName *importName = m.module().importArgumentName(); |
| if (!importName) |
| return m.fail(coercedExpr, "cannot import without an asm.js foreign parameter"); |
| if (!IsUseOfName(base, importName)) |
| return m.failName(coercedExpr, "base of import expression must be '%s'", importName); |
| |
| return m.addGlobalVarImport(varName, field, coercion); |
| } |
| |
| static bool |
| CheckNewArrayView(ModuleCompiler &m, PropertyName *varName, ParseNode *newExpr, bool first) |
| { |
| ParseNode *ctorExpr = ListHead(newExpr); |
| if (!ctorExpr->isKind(PNK_DOT)) |
| return m.fail(ctorExpr, "only valid 'new' import is 'new global.*Array(buf)'"); |
| |
| ParseNode *base = DotBase(ctorExpr); |
| PropertyName *field = DotMember(ctorExpr); |
| |
| PropertyName *globalName = m.module().globalArgumentName(); |
| if (!globalName) |
| return m.fail(base, "cannot create array view without an asm.js global parameter"); |
| if (!IsUseOfName(base, globalName)) |
| return m.failName(base, "expecting '%s.*Array", globalName); |
| |
| ParseNode *bufArg = NextNode(ctorExpr); |
| if (!bufArg || NextNode(bufArg) != NULL) |
| return m.fail(ctorExpr, "array view constructor takes exactly one argument"); |
| |
| PropertyName *bufferName = m.module().bufferArgumentName(); |
| if (!bufferName) |
| return m.fail(bufArg, "cannot create array view without an asm.js heap parameter"); |
| if (!IsUseOfName(bufArg, bufferName)) |
| return m.failName(bufArg, "argument to array view constructor must be '%s'", bufferName); |
| |
| JSAtomState &names = m.cx()->names(); |
| ArrayBufferView::ViewType type; |
| if (field == names.Int8Array) |
| type = ArrayBufferView::TYPE_INT8; |
| else if (field == names.Uint8Array) |
| type = ArrayBufferView::TYPE_UINT8; |
| else if (field == names.Int16Array) |
| type = ArrayBufferView::TYPE_INT16; |
| else if (field == names.Uint16Array) |
| type = ArrayBufferView::TYPE_UINT16; |
| else if (field == names.Int32Array) |
| type = ArrayBufferView::TYPE_INT32; |
| else if (field == names.Uint32Array) |
| type = ArrayBufferView::TYPE_UINT32; |
| else if (field == names.Float32Array) |
| type = ArrayBufferView::TYPE_FLOAT32; |
| else if (field == names.Float64Array) |
| type = ArrayBufferView::TYPE_FLOAT64; |
| else |
| return m.fail(ctorExpr, "could not match typed array name"); |
| |
| return m.addArrayView(varName, type, field); |
| } |
| |
| static bool |
| CheckGlobalDotImport(ModuleCompiler &m, PropertyName *varName, ParseNode *initNode) |
| { |
| ParseNode *base = DotBase(initNode); |
| PropertyName *field = DotMember(initNode); |
| |
| if (base->isKind(PNK_DOT)) { |
| ParseNode *global = DotBase(base); |
| PropertyName *math = DotMember(base); |
| if (!IsUseOfName(global, m.module().globalArgumentName()) || math != m.cx()->names().Math) |
| return m.fail(base, "expecting global.Math"); |
| |
| AsmJSMathBuiltin mathBuiltin; |
| if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) |
| return m.failName(initNode, "'%s' is not a standard Math builtin", field); |
| |
| return m.addMathBuiltin(varName, mathBuiltin, field); |
| } |
| |
| if (IsUseOfName(base, m.module().globalArgumentName())) { |
| if (field == m.cx()->names().NaN) |
| return m.addGlobalConstant(varName, js_NaN, field); |
| if (field == m.cx()->names().Infinity) |
| return m.addGlobalConstant(varName, js_PositiveInfinity, field); |
| return m.failName(initNode, "'%s' is not a standard global constant", field); |
| } |
| |
| if (IsUseOfName(base, m.module().importArgumentName())) |
| return m.addFFI(varName, field); |
| |
| return m.fail(initNode, "expecting c.y where c is either the global or foreign parameter"); |
| } |
| |
| static bool |
| CheckModuleGlobal(ModuleCompiler &m, ParseNode *var, bool first) |
| { |
| if (!IsDefinition(var)) |
| return m.fail(var, "import variable names must be unique"); |
| |
| if (!CheckModuleLevelName(m, var->name(), var)) |
| return false; |
| |
| ParseNode *initNode = MaybeDefinitionInitializer(var); |
| if (!initNode) |
| return m.fail(var, "module import needs initializer"); |
| |
| if (IsNumericLiteral(initNode)) |
| return CheckGlobalVariableInitConstant(m, var->name(), initNode); |
| |
| if (initNode->isKind(PNK_BITOR) || initNode->isKind(PNK_POS)) |
| return CheckGlobalVariableInitImport(m, var->name(), initNode); |
| |
| if (initNode->isKind(PNK_NEW)) |
| return CheckNewArrayView(m, var->name(), initNode, first); |
| |
| if (initNode->isKind(PNK_DOT)) |
| return CheckGlobalDotImport(m, var->name(), initNode); |
| |
| return m.fail(initNode, "unsupported import expression"); |
| } |
| |
| static bool |
| CheckModuleGlobals(ModuleCompiler &m, ParseNode **stmtIter) |
| { |
| ParseNode *stmt = SkipEmptyStatements(*stmtIter); |
| |
| bool first = true; |
| |
| for (; stmt && stmt->isKind(PNK_VAR); stmt = NextNonEmptyStatement(stmt)) { |
| for (ParseNode *var = VarListHead(stmt); var; var = NextNode(var)) { |
| if (!CheckModuleGlobal(m, var, first)) |
| return false; |
| first = false; |
| } |
| } |
| |
| *stmtIter = stmt; |
| return true; |
| } |
| |
| static bool |
| ArgFail(ModuleCompiler &m, PropertyName *argName, ParseNode *stmt) |
| { |
| return m.failName(stmt, "expecting argument type declaration for '%s' of the " |
| "form 'arg = arg|0' or 'arg = +arg'", argName); |