blob: 774019efc75071ca1893e46c561232893133e462 [file] [log] [blame]
/* -*- 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);