#include "wasm/AsmJS.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Compression.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"
#include "mozilla/Utf8.h"
#include "mozilla/Variant.h"
#include <new>
#include "jsmath.h"
#include "jsutil.h"
#include "builtin/String.h"
#include "frontend/ParseNode.h"
#include "frontend/Parser.h"
#include "gc/Policy.h"
#include "js/BuildId.h"
#include "js/MemoryMetrics.h"
#include "js/Printf.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "vm/ErrorReporting.h"
#include "vm/SelfHosting.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "wasm/WasmCompile.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmValidate.h"
#include "frontend/SharedContext-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
using namespace js;
using namespace js::frontend;
using namespace js::jit;
using namespace js::wasm;
using JS::AsmJSOption;
using JS::AutoStableStringChars;
using JS::GenericNaN;
using JS::SourceOwnership;
using JS::SourceText;
using mozilla::Abs;
using mozilla::ArrayEqual;
using mozilla::AsVariant;
using mozilla::CeilingLog2;
using mozilla::HashGeneric;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using mozilla::IsPositiveZero;
using mozilla::IsPowerOfTwo;
using mozilla::PodZero;
using mozilla::PositiveInfinity;
using mozilla::Unused;
using mozilla::Utf8Unit;
using mozilla::Compression::LZ4;
static const size_t MinHeapLength = PageSize;
static uint32_t RoundUpToNextValidAsmJSHeapLength(uint32_t length) {
if (length <= MinHeapLength) {
return MinHeapLength;
}
return wasm::RoundUpToNextValidARMImmediate(length);
}
enum AsmJSMathBuiltinFunction {
AsmJSMathBuiltin_sin,
AsmJSMathBuiltin_cos,
AsmJSMathBuiltin_tan,
AsmJSMathBuiltin_asin,
AsmJSMathBuiltin_acos,
AsmJSMathBuiltin_atan,
AsmJSMathBuiltin_ceil,
AsmJSMathBuiltin_floor,
AsmJSMathBuiltin_exp,
AsmJSMathBuiltin_log,
AsmJSMathBuiltin_pow,
AsmJSMathBuiltin_sqrt,
AsmJSMathBuiltin_abs,
AsmJSMathBuiltin_atan2,
AsmJSMathBuiltin_imul,
AsmJSMathBuiltin_fround,
AsmJSMathBuiltin_min,
AsmJSMathBuiltin_max,
AsmJSMathBuiltin_clz32
};
struct LitValPOD {
PackedTypeCode valType_;
union U {
uint32_t u32_;
uint64_t u64_;
float f32_;
double f64_;
} u;
LitValPOD() = default;
explicit LitValPOD(uint32_t u32) : valType_(ValType(ValType::I32).packed()) {
u.u32_ = u32;
}
explicit LitValPOD(uint64_t u64) : valType_(ValType(ValType::I64).packed()) {
u.u64_ = u64;
}
explicit LitValPOD(float f32) : valType_(ValType(ValType::F32).packed()) {
u.f32_ = f32;
}
explicit LitValPOD(double f64) : valType_(ValType(ValType::F64).packed()) {
u.f64_ = f64;
}
LitVal asLitVal() const {
switch (UnpackTypeCodeType(valType_)) {
case TypeCode::I32:
return LitVal(u.u32_);
case TypeCode::I64:
return LitVal(u.u64_);
case TypeCode::F32:
return LitVal(u.f32_);
case TypeCode::F64:
return LitVal(u.f64_);
default:
MOZ_CRASH("Can't happen");
}
}
};
static_assert(std::is_pod<LitValPOD>::value,
"must be POD to be simply serialized/deserialized");
class AsmJSGlobal {
public:
enum Which {
Variable,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction,
Constant
};
enum VarInitKind { InitConstant, InitImport };
enum ConstantKind { GlobalConstant, MathConstant };
private:
struct CacheablePod {
Which which_;
union V {
struct {
VarInitKind initKind_;
union U {
PackedTypeCode importValType_;
LitValPOD val_;
} u;
} var;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
struct {
ConstantKind kind_;
double value_;
} constant;
} u;
} pod;
CacheableChars field_;
friend class ModuleValidatorShared;
template <typename Unit>
friend class ModuleValidator;
public:
AsmJSGlobal() = default;
AsmJSGlobal(Which which, UniqueChars field) {
mozilla::PodZero(&pod); pod.which_ = which;
field_ = std::move(field);
}
const char* field() const { return field_.get(); }
Which which() const { return pod.which_; }
VarInitKind varInitKind() const {
MOZ_ASSERT(pod.which_ == Variable);
return pod.u.var.initKind_;
}
LitValPOD varInitVal() const {
MOZ_ASSERT(pod.which_ == Variable);
MOZ_ASSERT(pod.u.var.initKind_ == InitConstant);
return pod.u.var.u.val_;
}
ValType varInitImportType() const {
MOZ_ASSERT(pod.which_ == Variable);
MOZ_ASSERT(pod.u.var.initKind_ == InitImport);
return ValType(pod.u.var.u.importValType_);
}
uint32_t ffiIndex() const {
MOZ_ASSERT(pod.which_ == FFI);
return pod.u.ffiIndex_;
}
Scalar::Type viewType() const {
MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor);
return pod.u.viewType_;
}
AsmJSMathBuiltinFunction mathBuiltinFunction() const {
MOZ_ASSERT(pod.which_ == MathBuiltinFunction);
return pod.u.mathBuiltinFunc_;
}
ConstantKind constantKind() const {
MOZ_ASSERT(pod.which_ == Constant);
return pod.u.constant.kind_;
}
double constantValue() const {
MOZ_ASSERT(pod.which_ == Constant);
return pod.u.constant.value_;
}
};
typedef Vector<AsmJSGlobal, 0, SystemAllocPolicy> AsmJSGlobalVector;
class AsmJSImport {
uint32_t ffiIndex_;
public:
AsmJSImport() = default;
explicit AsmJSImport(uint32_t ffiIndex) : ffiIndex_(ffiIndex) {}
uint32_t ffiIndex() const { return ffiIndex_; }
};
typedef Vector<AsmJSImport, 0, SystemAllocPolicy> AsmJSImportVector;
class AsmJSExport {
uint32_t funcIndex_ = 0;
uint32_t startOffsetInModule_ = 0; uint32_t endOffsetInModule_ = 0;
public:
AsmJSExport() = default;
AsmJSExport(uint32_t funcIndex, uint32_t startOffsetInModule,
uint32_t endOffsetInModule)
: funcIndex_(funcIndex),
startOffsetInModule_(startOffsetInModule),
endOffsetInModule_(endOffsetInModule) {}
uint32_t funcIndex() const { return funcIndex_; }
uint32_t startOffsetInModule() const { return startOffsetInModule_; }
uint32_t endOffsetInModule() const { return endOffsetInModule_; }
};
typedef Vector<AsmJSExport, 0, SystemAllocPolicy> AsmJSExportVector;
struct AsmJSMetadataCacheablePod {
uint32_t numFFIs = 0;
uint32_t srcLength = 0;
uint32_t srcLengthWithRightBrace = 0;
AsmJSMetadataCacheablePod() = default;
};
struct js::AsmJSMetadata : Metadata, AsmJSMetadataCacheablePod {
AsmJSGlobalVector asmJSGlobals;
AsmJSImportVector asmJSImports;
AsmJSExportVector asmJSExports;
CacheableCharsVector asmJSFuncNames;
CacheableChars globalArgumentName;
CacheableChars importArgumentName;
CacheableChars bufferArgumentName;
uint32_t toStringStart;
uint32_t srcStart;
bool strict;
ScriptSourceHolder scriptSource;
uint32_t srcEndBeforeCurly() const { return srcStart + srcLength; }
uint32_t srcEndAfterCurly() const {
return srcStart + srcLengthWithRightBrace;
}
AsmJSMetadata()
: Metadata(ModuleKind::AsmJS),
toStringStart(0),
srcStart(0),
strict(false) {}
~AsmJSMetadata() override {}
const AsmJSExport& lookupAsmJSExport(uint32_t funcIndex) const {
for (const AsmJSExport& exp : asmJSExports) {
if (exp.funcIndex() == funcIndex) {
return exp;
}
}
MOZ_CRASH("missing asm.js func export");
}
bool mutedErrors() const override {
return scriptSource.get()->mutedErrors();
}
const char16_t* displayURL() const override {
return scriptSource.get()->hasDisplayURL()
? scriptSource.get()->displayURL()
: nullptr;
}
ScriptSource* maybeScriptSource() const override {
return scriptSource.get();
}
bool getFuncName(NameContext ctx, uint32_t funcIndex,
UTF8Bytes* name) const override {
const char* p = asmJSFuncNames[funcIndex].get();
if (!p) {
return true;
}
return name->append(p, strlen(p));
}
AsmJSMetadataCacheablePod& pod() { return *this; }
const AsmJSMetadataCacheablePod& pod() const { return *this; }
};
typedef RefPtr<AsmJSMetadata> MutableAsmJSMetadata;
static inline ParseNode* NextNode(ParseNode* pn) { return pn->pn_next; }
static inline ParseNode* UnaryKid(ParseNode* pn) {
return pn->as<UnaryNode>().kid();
}
static inline ParseNode* BinaryRight(ParseNode* pn) {
return pn->as<BinaryNode>().right();
}
static inline ParseNode* BinaryLeft(ParseNode* pn) {
return pn->as<BinaryNode>().left();
}
static inline ParseNode* ReturnExpr(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ReturnStmt));
return UnaryKid(pn);
}
static inline ParseNode* TernaryKid1(ParseNode* pn) {
return pn->as<TernaryNode>().kid1();
}
static inline ParseNode* TernaryKid2(ParseNode* pn) {
return pn->as<TernaryNode>().kid2();
}
static inline ParseNode* TernaryKid3(ParseNode* pn) {
return pn->as<TernaryNode>().kid3();
}
static inline ParseNode* ListHead(ParseNode* pn) {
return pn->as<ListNode>().head();
}
static inline unsigned ListLength(ParseNode* pn) {
return pn->as<ListNode>().count();
}
static inline ParseNode* CallCallee(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return BinaryLeft(pn);
}
static inline unsigned CallArgListLength(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return ListLength(BinaryRight(pn));
}
static inline ParseNode* CallArgList(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return ListHead(BinaryRight(pn));
}
static inline ParseNode* VarListHead(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::VarStmt) ||
pn->isKind(ParseNodeKind::ConstDecl));
return ListHead(pn);
}
static inline bool IsDefaultCase(ParseNode* pn) {
return pn->as<CaseClause>().isDefault();
}
static inline ParseNode* CaseExpr(ParseNode* pn) {
return pn->as<CaseClause>().caseExpression();
}
static inline ParseNode* CaseBody(ParseNode* pn) {
return pn->as<CaseClause>().statementList();
}
static inline ParseNode* BinaryOpLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isBinaryOperation());
MOZ_ASSERT(pn->as<ListNode>().count() == 2);
return ListHead(pn);
}
static inline ParseNode* BinaryOpRight(ParseNode* pn) {
MOZ_ASSERT(pn->isBinaryOperation());
MOZ_ASSERT(pn->as<ListNode>().count() == 2);
return NextNode(ListHead(pn));
}
static inline ParseNode* BitwiseLeft(ParseNode* pn) { return BinaryOpLeft(pn); }
static inline ParseNode* BitwiseRight(ParseNode* pn) {
return BinaryOpRight(pn);
}
static inline ParseNode* MultiplyLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* MultiplyRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* AddSubLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
pn->isKind(ParseNodeKind::SubExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* AddSubRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
pn->isKind(ParseNodeKind::SubExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* DivOrModLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
pn->isKind(ParseNodeKind::ModExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* DivOrModRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
pn->isKind(ParseNodeKind::ModExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* ComparisonLeft(ParseNode* pn) {
return BinaryOpLeft(pn);
}
static inline ParseNode* ComparisonRight(ParseNode* pn) {
return BinaryOpRight(pn);
}
static inline bool IsExpressionStatement(ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt);
}
static inline ParseNode* ExpressionStatementExpr(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStmt));
return UnaryKid(pn);
}
static inline PropertyName* LoopControlMaybeLabel(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) ||
pn->isKind(ParseNodeKind::ContinueStmt));
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) {
return pn->as<NumericLiteral>().value();
}
static bool NumberNodeHasFrac(ParseNode* pn) {
return pn->as<NumericLiteral>().decimalPoint() == HasDecimal;
}
static ParseNode* DotBase(ParseNode* pn) {
return &pn->as<PropertyAccess>().expression();
}
static PropertyName* DotMember(ParseNode* pn) {
return &pn->as<PropertyAccess>().name();
}
static ParseNode* ElemBase(ParseNode* pn) {
return &pn->as<PropertyByValue>().expression();
}
static ParseNode* ElemIndex(ParseNode* pn) {
return &pn->as<PropertyByValue>().key();
}
static inline JSFunction* FunctionObject(FunctionNode* funNode) {
return funNode->funbox()->function();
}
static inline PropertyName* FunctionName(FunctionNode* funNode) {
if (JSAtom* name = FunctionObject(funNode)->explicitName()) {
return name->asPropertyName();
}
return nullptr;
}
static inline ParseNode* FunctionStatementList(FunctionNode* funNode) {
MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody));
LexicalScopeNode* last =
&funNode->body()->as<ListNode>().last()->as<LexicalScopeNode>();
MOZ_ASSERT(last->isEmptyScope());
ParseNode* body = last->scopeBody();
MOZ_ASSERT(body->isKind(ParseNodeKind::StatementList));
return body;
}
static inline bool IsNormalObjectField(ParseNode* pn) {
return pn->isKind(ParseNodeKind::Colon) && pn->getOp() == JSOP_INITPROP &&
BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName);
}
static inline PropertyName* ObjectNormalFieldName(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName));
return BinaryLeft(pn)->as<NameNode>().atom()->asPropertyName();
}
static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
return BinaryRight(pn);
}
static inline bool IsUseOfName(ParseNode* pn, PropertyName* name) {
return pn->isName(name);
}
static inline bool IsIgnoredDirectiveName(JSContext* cx, JSAtom* atom) {
return atom != cx->names().useStrict;
}
static inline bool IsIgnoredDirective(JSContext* cx, ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt) &&
UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) &&
IsIgnoredDirectiveName(cx, UnaryKid(pn)->as<NameNode>().atom());
}
static inline bool IsEmptyStatement(ParseNode* pn) {
return pn->isKind(ParseNodeKind::EmptyStmt);
}
static inline ParseNode* SkipEmptyStatements(ParseNode* pn) {
while (pn && IsEmptyStatement(pn)) {
pn = pn->pn_next;
}
return pn;
}
static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
return SkipEmptyStatements(pn->pn_next);
}
template <typename Unit>
static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
auto& ts = parser.tokenStream;
TokenKind tk;
while (true) {
if (!ts.getToken(&tk, TokenStreamShared::Operand)) {
return false;
}
if (tk != TokenKind::Semi) {
break;
}
}
*tkp = tk;
return true;
}
template <typename Unit>
static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
auto& ts = parser.tokenStream;
TokenKind tk;
while (true) {
if (!ts.peekToken(&tk, TokenStream::Operand)) {
return false;
}
if (tk != TokenKind::Semi) {
break;
}
ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::Operand);
}
*tkp = tk;
return true;
}
template <typename Unit>
static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
ParseNode** var) {
TokenKind tk;
if (!PeekToken(parser, &tk)) {
return false;
}
if (tk != TokenKind::Var && tk != TokenKind::Const) {
*var = nullptr;
return true;
}
*var = parser.statementListItem(YieldIsName);
if (!*var) {
return false;
}
MOZ_ASSERT((*var)->isKind(ParseNodeKind::VarStmt) ||
(*var)->isKind(ParseNodeKind::ConstDecl));
return true;
}
class NumLit {
public:
enum Which {
Fixnum,
NegativeInt,
BigUnsigned,
Double,
Float,
OutOfRangeInt = -1
};
private:
Which which_;
JS::Value value_;
public:
NumLit() = default;
NumLit(Which w, const Value& v) : which_(w), value_(v) {}
Which which() const { return which_; }
int32_t toInt32() const {
MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt ||
which_ == BigUnsigned);
return value_.toInt32();
}
uint32_t toUint32() const { return (uint32_t)toInt32(); }
double toDouble() const {
MOZ_ASSERT(which_ == Double);
return value_.toDouble();
}
float toFloat() const {
MOZ_ASSERT(which_ == Float);
return float(value_.toDouble());
}
Value scalarValue() const {
MOZ_ASSERT(which_ != OutOfRangeInt);
return value_;
}
bool valid() const { return which_ != OutOfRangeInt; }
bool isZeroBits() const {
MOZ_ASSERT(valid());
switch (which()) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return toInt32() == 0;
case NumLit::Double:
return IsPositiveZero(toDouble());
case NumLit::Float:
return IsPositiveZero(toFloat());
case NumLit::OutOfRangeInt:
MOZ_CRASH("can't be here because of valid() check above");
}
return false;
}
LitValPOD value() const {
switch (which_) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return LitValPOD(toUint32());
case NumLit::Float:
return LitValPOD(toFloat());
case NumLit::Double:
return LitValPOD(toDouble());
case NumLit::OutOfRangeInt:;
}
MOZ_CRASH("bad literal");
}
};
class Type {
public:
enum Which {
Fixnum = NumLit::Fixnum,
Signed = NumLit::NegativeInt,
Unsigned = NumLit::BigUnsigned,
DoubleLit = NumLit::Double,
Float = NumLit::Float,
Double,
MaybeDouble,
MaybeFloat,
Floatish,
Int,
Intish,
Void
};
private:
Which which_;
public:
Type() = default;
MOZ_IMPLICIT Type(Which w) : which_(w) {}
static Type ret(Type t) {
MOZ_ASSERT(t.isCanonical());
return t.isInt() ? Signed : t;
}
static Type lit(const NumLit& lit) {
MOZ_ASSERT(lit.valid());
Which which = Type::Which(lit.which());
MOZ_ASSERT(which >= Fixnum && which <= Float);
Type t;
t.which_ = which;
return t;
}
static Type canonicalize(Type t) {
switch (t.which()) {
case Fixnum:
case Signed:
case Unsigned:
case Int:
return Int;
case Float:
return Float;
case DoubleLit:
case Double:
return Double;
case Void:
return Void;
case MaybeDouble:
case MaybeFloat:
case Floatish:
case Intish:
break;
}
MOZ_CRASH("Invalid vartype");
}
Which which() const { return which_; }
bool operator==(Type rhs) const { return which_ == rhs.which_; }
bool operator!=(Type rhs) const { return which_ != rhs.which_; }
bool operator<=(Type rhs) const {
switch (rhs.which_) {
case Signed:
return isSigned();
case Unsigned:
return isUnsigned();
case DoubleLit:
return isDoubleLit();
case Double:
return isDouble();
case Float:
return isFloat();
case MaybeDouble:
return isMaybeDouble();
case MaybeFloat:
return isMaybeFloat();
case Floatish:
return isFloatish();
case Int:
return isInt();
case Intish:
return isIntish();
case Fixnum:
return isFixnum();
case Void:
return isVoid();
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected rhs type");
}
bool isFixnum() const { return which_ == Fixnum; }
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 isDoubleLit() const { return which_ == DoubleLit; }
bool isDouble() const { return isDoubleLit() || which_ == Double; }
bool isMaybeDouble() const { return isDouble() || which_ == MaybeDouble; }
bool isFloat() const { return which_ == Float; }
bool isMaybeFloat() const { return isFloat() || which_ == MaybeFloat; }
bool isFloatish() const { return isMaybeFloat() || which_ == Floatish; }
bool isVoid() const { return which_ == Void; }
bool isExtern() const { return isDouble() || isSigned(); }
bool isArgType() const { return isInt() || isFloat() || isDouble(); }
bool isReturnType() const {
return isSigned() || isFloat() || isDouble() || isVoid();
}
bool isGlobalVarType() const { return isArgType(); }
bool isCanonical() const {
switch (which()) {
case Int:
case Float:
case Double:
case Void:
return true;
default:
return false;
}
}
bool isCanonicalValType() const { return !isVoid() && isCanonical(); }
ExprType canonicalToExprType() const {
switch (which()) {
case Int:
return ExprType::I32;
case Float:
return ExprType::F32;
case Double:
return ExprType::F64;
case Void:
return ExprType::Void;
default:
MOZ_CRASH("Need canonical type");
}
}
ValType canonicalToValType() const {
return NonVoidToValType(canonicalToExprType());
}
ExprType toWasmBlockSignatureType() const {
switch (which()) {
case Fixnum:
case Signed:
case Unsigned:
case Int:
case Intish:
return ExprType::I32;
case Float:
case MaybeFloat:
case Floatish:
return ExprType::F32;
case DoubleLit:
case Double:
case MaybeDouble:
return ExprType::F64;
case Void:
return ExprType::Void;
}
MOZ_CRASH("Invalid Type");
}
const char* toChars() const {
switch (which_) {
case Double:
return "double";
case DoubleLit:
return "doublelit";
case MaybeDouble:
return "double?";
case Float:
return "float";
case Floatish:
return "floatish";
case MaybeFloat:
return "float?";
case Fixnum:
return "fixnum";
case Int:
return "int";
case Signed:
return "signed";
case Unsigned:
return "unsigned";
case Intish:
return "intish";
case Void:
return "void";
}
MOZ_CRASH("Invalid Type");
}
};
static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidatorShared {
public:
class Func {
PropertyName* name_;
uint32_t sigIndex_;
uint32_t firstUse_;
uint32_t funcDefIndex_;
bool defined_;
uint32_t srcBegin_;
uint32_t srcEnd_;
uint32_t line_;
Bytes bytes_;
Uint32Vector callSiteLineNums_;
public:
Func(PropertyName* name, uint32_t sigIndex, uint32_t firstUse,
uint32_t funcDefIndex)
: name_(name),
sigIndex_(sigIndex),
firstUse_(firstUse),
funcDefIndex_(funcDefIndex),
defined_(false),
srcBegin_(0),
srcEnd_(0),
line_(0) {}
PropertyName* name() const { return name_; }
uint32_t sigIndex() const { return sigIndex_; }
uint32_t firstUse() const { return firstUse_; }
bool defined() const { return defined_; }
uint32_t funcDefIndex() const { return funcDefIndex_; }
void define(ParseNode* fn, uint32_t line, Bytes&& bytes,
Uint32Vector&& callSiteLineNums) {
MOZ_ASSERT(!defined_);
defined_ = true;
srcBegin_ = fn->pn_pos.begin;
srcEnd_ = fn->pn_pos.end;
line_ = line;
bytes_ = std::move(bytes);
callSiteLineNums_ = std::move(callSiteLineNums);
}
uint32_t srcBegin() const {
MOZ_ASSERT(defined_);
return srcBegin_;
}
uint32_t srcEnd() const {
MOZ_ASSERT(defined_);
return srcEnd_;
}
uint32_t line() const {
MOZ_ASSERT(defined_);
return line_;
}
const Bytes& bytes() const {
MOZ_ASSERT(defined_);
return bytes_;
}
Uint32Vector& callSiteLineNums() {
MOZ_ASSERT(defined_);
return callSiteLineNums_;
}
};
using ConstFuncVector = Vector<const Func*>;
using FuncVector = Vector<Func>;
class Table {
uint32_t sigIndex_;
PropertyName* name_;
uint32_t firstUse_;
uint32_t mask_;
bool defined_;
Table(Table&& rhs) = delete;
public:
Table(uint32_t sigIndex, PropertyName* name, uint32_t firstUse,
uint32_t mask)
: sigIndex_(sigIndex),
name_(name),
firstUse_(firstUse),
mask_(mask),
defined_(false) {}
uint32_t sigIndex() const { return sigIndex_; }
PropertyName* name() const { return name_; }
uint32_t firstUse() const { return firstUse_; }
unsigned mask() const { return mask_; }
bool defined() const { return defined_; }
void define() {
MOZ_ASSERT(!defined_);
defined_ = true;
}
};
using TableVector = Vector<Table*>;
class Global {
public:
enum Which {
Variable,
ConstantLiteral,
ConstantImport,
Function,
Table,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction
};
private:
Which which_;
union U {
struct VarOrConst {
Type::Which type_;
unsigned index_;
NumLit literalValue_;
VarOrConst(unsigned index, const NumLit& lit)
: type_(Type::lit(lit).which()),
index_(index),
literalValue_(lit) {}
VarOrConst(unsigned index, Type::Which which)
: type_(which), index_(index) {
}
explicit VarOrConst(double constant)
: type_(Type::Double),
literalValue_(NumLit::Double, DoubleValue(constant)) {
}
} varOrConst;
uint32_t funcDefIndex_;
uint32_t tableIndex_;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
U() : funcDefIndex_(0) {}
MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
} u;
friend class ModuleValidatorShared;
template <typename Unit>
friend class ModuleValidator;
friend class js::LifoAlloc;
explicit Global(Which which) : which_(which) {}
public:
Which which() const { return which_; }
Type varOrConstType() const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantLiteral ||
which_ == ConstantImport);
return u.varOrConst.type_;
}
unsigned varOrConstIndex() const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
return u.varOrConst.index_;
}
bool isConst() const {
return which_ == ConstantLiteral || which_ == ConstantImport;
}
NumLit constLiteralValue() const {
MOZ_ASSERT(which_ == ConstantLiteral);
return u.varOrConst.literalValue_;
}
uint32_t funcDefIndex() const {
MOZ_ASSERT(which_ == Function);
return u.funcDefIndex_;
}
uint32_t tableIndex() const {
MOZ_ASSERT(which_ == Table);
return u.tableIndex_;
}
unsigned ffiIndex() const {
MOZ_ASSERT(which_ == FFI);
return u.ffiIndex_;
}
bool isAnyArrayView() const {
return which_ == ArrayView || which_ == ArrayViewCtor;
}
Scalar::Type viewType() const {
MOZ_ASSERT(isAnyArrayView());
return u.viewType_;
}
bool isMathFunction() const { return which_ == MathBuiltinFunction; }
AsmJSMathBuiltinFunction mathBuiltinFunction() const {
MOZ_ASSERT(which_ == MathBuiltinFunction);
return u.mathBuiltinFunc_;
}
};
struct MathBuiltin {
enum Kind { Function, Constant };
Kind kind;
union {
double cst;
AsmJSMathBuiltinFunction func;
} u;
MathBuiltin() : kind(Kind(-1)), u{} {}
explicit MathBuiltin(double cst) : kind(Constant) { u.cst = cst; }
explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
u.func = func;
}
};
struct ArrayView {
ArrayView(PropertyName* name, Scalar::Type type) : name(name), type(type) {}
PropertyName* name;
Scalar::Type type;
};
protected:
class HashableSig {
uint32_t sigIndex_;
const TypeDefVector& types_;
public:
HashableSig(uint32_t sigIndex, const TypeDefVector& types)
: sigIndex_(sigIndex), types_(types) {}
uint32_t sigIndex() const { return sigIndex_; }
const FuncType& funcType() const { return types_[sigIndex_].funcType(); }
using Lookup = const FuncType&;
static HashNumber hash(Lookup l) { return l.hash(); }
static bool match(HashableSig lhs, Lookup rhs) {
return lhs.funcType() == rhs;
}
};
class NamedSig : public HashableSig {
PropertyName* name_;
public:
NamedSig(PropertyName* name, uint32_t sigIndex, const TypeDefVector& types)
: HashableSig(sigIndex, types), name_(name) {}
PropertyName* name() const { return name_; }
struct Lookup {
PropertyName* name;
const FuncType& funcType;
Lookup(PropertyName* name, const FuncType& funcType)
: name(name), funcType(funcType) {}
};
static HashNumber hash(Lookup l) {
return HashGeneric(l.name, l.funcType.hash());
}
static bool match(NamedSig lhs, Lookup rhs) {
return lhs.name() == rhs.name && lhs.funcType() == rhs.funcType;
}
};
using SigSet = HashSet<HashableSig, HashableSig>;
using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
using GlobalMap = HashMap<PropertyName*, Global*>;
using MathNameMap = HashMap<PropertyName*, MathBuiltin>;
using ArrayViewVector = Vector<ArrayView>;
protected:
JSContext* cx_;
FunctionNode* moduleFunctionNode_;
PropertyName* moduleFunctionName_;
PropertyName* globalArgumentName_ = nullptr;
PropertyName* importArgumentName_ = nullptr;
PropertyName* bufferArgumentName_ = nullptr;
MathNameMap standardLibraryMathNames_;
RootedFunction dummyFunction_;
LifoAlloc validationLifo_;
FuncVector funcDefs_;
TableVector tables_;
GlobalMap globalMap_;
SigSet sigSet_;
FuncImportMap funcImportMap_;
ArrayViewVector arrayViews_;
CompilerEnvironment compilerEnv_;
ModuleEnvironment env_;
MutableAsmJSMetadata asmJSMetadata_;
UniqueChars errorString_ = nullptr;
uint32_t errorOffset_ = UINT32_MAX;
bool errorOverRecursed_ = false;
protected:
ModuleValidatorShared(JSContext* cx, FunctionNode* moduleFunctionNode)
: cx_(cx),
moduleFunctionNode_(moduleFunctionNode),
moduleFunctionName_(FunctionName(moduleFunctionNode)),
standardLibraryMathNames_(cx),
dummyFunction_(cx),
validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE),
funcDefs_(cx),
tables_(cx),
globalMap_(cx),
sigSet_(cx),
funcImportMap_(cx),
arrayViews_(cx),
compilerEnv_(CompileMode::Once, Tier::Optimized, OptimizedBackend::Ion,
DebugEnabled::False, false),
env_( false, &compilerEnv_, Shareable::False,
ModuleKind::AsmJS) {
compilerEnv_.computeParameters( false);
env_.minMemoryLength = RoundUpToNextValidAsmJSHeapLength(0);
}
protected:
MOZ_MUST_USE bool addStandardLibraryMathInfo() {
static constexpr struct {
const char* name;
AsmJSMathBuiltinFunction func;
} functions[] = {
{"sin", AsmJSMathBuiltin_sin}, {"cos", AsmJSMathBuiltin_cos},
{"tan", AsmJSMathBuiltin_tan}, {"asin", AsmJSMathBuiltin_asin},
{"acos", AsmJSMathBuiltin_acos}, {"atan", AsmJSMathBuiltin_atan},
{"ceil", AsmJSMathBuiltin_ceil}, {"floor", AsmJSMathBuiltin_floor},
{"exp", AsmJSMathBuiltin_exp}, {"log", AsmJSMathBuiltin_log},
{"pow", AsmJSMathBuiltin_pow}, {"sqrt", AsmJSMathBuiltin_sqrt},
{"abs", AsmJSMathBuiltin_abs}, {"atan2", AsmJSMathBuiltin_atan2},
{"imul", AsmJSMathBuiltin_imul}, {"clz32", AsmJSMathBuiltin_clz32},
{"fround", AsmJSMathBuiltin_fround}, {"min", AsmJSMathBuiltin_min},
{"max", AsmJSMathBuiltin_max},
};
auto AddMathFunction = [this](const char* name,
AsmJSMathBuiltinFunction func) {
JSAtom* atom = Atomize(cx_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(func);
return this->standardLibraryMathNames_.putNew(atom->asPropertyName(),
builtin);
};
for (const auto& info : functions) {
if (!AddMathFunction(info.name, info.func)) {
return false;
}
}
static constexpr struct {
const char* name;
double value;
} constants[] = {
{"E", M_E},
{"LN10", M_LN10},
{"LN2", M_LN2},
{"LOG2E", M_LOG2E},
{"LOG10E", M_LOG10E},
{"PI", M_PI},
{"SQRT1_2", M_SQRT1_2},
{"SQRT2", M_SQRT2},
};
auto AddMathConstant = [this](const char* name, double cst) {
JSAtom* atom = Atomize(cx_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(cst);
return this->standardLibraryMathNames_.putNew(atom->asPropertyName(),
builtin);
};
for (const auto& info : constants) {
if (!AddMathConstant(info.name, info.value)) {
return false;
}
}
return true;
}
MOZ_MUST_USE bool initDummyFunction() {
dummyFunction_ = NewScriptedFunction(
cx_, 0, JSFunction::INTERPRETED, nullptr,
nullptr, gc::AllocKind::FUNCTION, TenuredObject);
if (!dummyFunction_) {
return false;
}
return true;
}
public:
JSContext* cx() const { return cx_; }
PropertyName* moduleFunctionName() const { return moduleFunctionName_; }
PropertyName* globalArgumentName() const { return globalArgumentName_; }
PropertyName* importArgumentName() const { return importArgumentName_; }
PropertyName* bufferArgumentName() const { return bufferArgumentName_; }
const ModuleEnvironment& env() { return env_; }
RootedFunction& dummyFunction() { return dummyFunction_; }
uint32_t minMemoryLength() const { return env_.minMemoryLength; }
void initModuleFunctionName(PropertyName* name) {
MOZ_ASSERT(!moduleFunctionName_);
moduleFunctionName_ = name;
}
MOZ_MUST_USE bool initGlobalArgumentName(PropertyName* n) {
globalArgumentName_ = n;
if (n) {
MOZ_ASSERT(n->isTenured());
asmJSMetadata_->globalArgumentName = StringToNewUTF8CharsZ(cx_, *n);
if (!asmJSMetadata_->globalArgumentName) {
return false;
}
}
return true;
}
MOZ_MUST_USE bool initImportArgumentName(PropertyName* n) {
importArgumentName_ = n;
if (n) {
MOZ_ASSERT(n->isTenured());
asmJSMetadata_->importArgumentName = StringToNewUTF8CharsZ(cx_, *n);
if (!asmJSMetadata_->importArgumentName) {
return false;
}
}
return true;
}
MOZ_MUST_USE bool initBufferArgumentName(PropertyName* n) {
bufferArgumentName_ = n;
if (n) {
MOZ_ASSERT(n->isTenured());
asmJSMetadata_->bufferArgumentName = StringToNewUTF8CharsZ(cx_, *n);
if (!asmJSMetadata_->bufferArgumentName) {
return false;
}
}
return true;
}
bool addGlobalVarInit(PropertyName* var, const NumLit& lit, Type type,
bool isConst) {
MOZ_ASSERT(type.isGlobalVarType());
MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit)));
uint32_t index = env_.globals.length();
if (!env_.globals.emplaceBack(type.canonicalToValType(), !isConst, index,
ModuleKind::AsmJS)) {
return false;
}
Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable;
Global* global = validationLifo_.new_<Global>(which);
if (!global) {
return false;
}
if (isConst) {
new (&global->u.varOrConst) Global::U::VarOrConst(index, lit);
} else {
new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
}
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Variable, nullptr);
g.pod.u.var.initKind_ = AsmJSGlobal::InitConstant;
g.pod.u.var.u.val_ = lit.value();
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addGlobalVarImport(PropertyName* var, PropertyName* field, Type type,
bool isConst) {
MOZ_ASSERT(type.isGlobalVarType());
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
uint32_t index = env_.globals.length();
ValType valType = type.canonicalToValType();
if (!env_.globals.emplaceBack(valType, !isConst, index,
ModuleKind::AsmJS)) {
return false;
}
Global::Which which = isConst ? Global::ConstantImport : Global::Variable;
Global* global = validationLifo_.new_<Global>(which);
if (!global) {
return false;
}
new (&global->u.varOrConst) Global::U::VarOrConst(index, type.which());
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Variable, std::move(fieldChars));
g.pod.u.var.initKind_ = AsmJSGlobal::InitImport;
g.pod.u.var.u.importValType_ = valType.packed();
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addArrayView(PropertyName* var, Scalar::Type vt,
PropertyName* maybeField) {
UniqueChars fieldChars;
if (maybeField) {
fieldChars = StringToNewUTF8CharsZ(cx_, *maybeField);
if (!fieldChars) {
return false;
}
}
if (!arrayViews_.append(ArrayView(var, vt))) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::ArrayView);
if (!global) {
return false;
}
new (&global->u.viewType_) Scalar::Type(vt);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::ArrayView, std::move(fieldChars));
g.pod.u.viewType_ = vt;
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addMathBuiltinFunction(PropertyName* var, AsmJSMathBuiltinFunction func,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::MathBuiltinFunction);
if (!global) {
return false;
}
new (&global->u.mathBuiltinFunc_) AsmJSMathBuiltinFunction(func);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::MathBuiltinFunction, std::move(fieldChars));
g.pod.u.mathBuiltinFunc_ = func;
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
private:
bool addGlobalDoubleConstant(PropertyName* var, double constant) {
Global* global = validationLifo_.new_<Global>(Global::ConstantLiteral);
if (!global) {
return false;
}
new (&global->u.varOrConst) Global::U::VarOrConst(constant);
return globalMap_.putNew(var, global);
}
public:
bool addMathBuiltinConstant(PropertyName* var, double constant,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
if (!addGlobalDoubleConstant(var, constant)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
g.pod.u.constant.value_ = constant;
g.pod.u.constant.kind_ = AsmJSGlobal::MathConstant;
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addGlobalConstant(PropertyName* var, double constant,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
if (!addGlobalDoubleConstant(var, constant)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::Constant, std::move(fieldChars));
g.pod.u.constant.value_ = constant;
g.pod.u.constant.kind_ = AsmJSGlobal::GlobalConstant;
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addArrayViewCtor(PropertyName* var, Scalar::Type vt,
PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::ArrayViewCtor);
if (!global) {
return false;
}
new (&global->u.viewType_) Scalar::Type(vt);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::ArrayViewCtor, std::move(fieldChars));
g.pod.u.viewType_ = vt;
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addFFI(PropertyName* var, PropertyName* field) {
UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field);
if (!fieldChars) {
return false;
}
if (asmJSMetadata_->numFFIs == UINT32_MAX) {
return false;
}
uint32_t ffiIndex = asmJSMetadata_->numFFIs++;
Global* global = validationLifo_.new_<Global>(Global::FFI);
if (!global) {
return false;
}
new (&global->u.ffiIndex_) uint32_t(ffiIndex);
if (!globalMap_.putNew(var, global)) {
return false;
}
AsmJSGlobal g(AsmJSGlobal::FFI, std::move(fieldChars));
g.pod.u.ffiIndex_ = ffiIndex;
return asmJSMetadata_->asmJSGlobals.append(std::move(g));
}
bool addExportField(const Func& func, PropertyName* maybeField) {
CacheableChars fieldChars;
if (maybeField) {
fieldChars = StringToNewUTF8CharsZ(cx_, *maybeField);
} else {
fieldChars = DuplicateString("");
}
if (!fieldChars) {
return false;
}
uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
if (!env_.exports.emplaceBack(std::move(fieldChars), funcIndex,
DefinitionKind::Function)) {
return false;
}
return asmJSMetadata_->asmJSExports.emplaceBack(
funcIndex, func.srcBegin() - asmJSMetadata_->srcStart,
func.srcEnd() - asmJSMetadata_->srcStart);
}
bool defineFuncPtrTable(uint32_t tableIndex, Uint32Vector&& elems) {
Table& table = *tables_[tableIndex];
if (table.defined()) {
return false;
}
table.define();
for (uint32_t& index : elems) {
index += funcImportMap_.count();
}
MutableElemSegment seg = js_new<ElemSegment>();
if (!seg) {
return false;
}
seg->tableIndex = tableIndex;
seg->offsetIfActive = Some(InitExpr(LitVal(uint32_t(0))));
seg->elemFuncIndices = std::move(elems);
return env_.elemSegments.append(std::move(seg));
}
bool tryConstantAccess(uint64_t start, uint64_t width) {
MOZ_ASSERT(UINT64_MAX - start > width);
uint64_t len = start + width;
if (len > uint64_t(INT32_MAX) + 1) {
return false;
}
len = RoundUpToNextValidAsmJSHeapLength(len);
if (len > env_.minMemoryLength) {
env_.minMemoryLength = len;
}
return true;
}
bool hasAlreadyFailed() const { return !!errorString_; }
bool failOffset(uint32_t offset, const char* str) {
MOZ_ASSERT(!hasAlreadyFailed());
MOZ_ASSERT(errorOffset_ == UINT32_MAX);
MOZ_ASSERT(str);
errorOffset_ = offset;
errorString_ = DuplicateString(str);
return false;
}
bool fail(ParseNode* pn, const char* str) {
return failOffset(pn->pn_pos.begin, str);
}
bool failfVAOffset(uint32_t offset, const char* fmt, va_list ap)
MOZ_FORMAT_PRINTF(3, 0) {
MOZ_ASSERT(!hasAlreadyFailed());
MOZ_ASSERT(errorOffset_ == UINT32_MAX);
MOZ_ASSERT(fmt);
errorOffset_ = offset;
errorString_ = JS_vsmprintf(fmt, ap);
return false;
}
bool failfOffset(uint32_t offset, const char* fmt, ...)
MOZ_FORMAT_PRINTF(3, 4) {
va_list ap;
va_start(ap, fmt);
failfVAOffset(offset, fmt, ap);
va_end(ap);
return false;
}
bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
va_list ap;
va_start(ap, fmt);
failfVAOffset(pn->pn_pos.begin, fmt, ap);
va_end(ap);
return false;
}
bool failNameOffset(uint32_t offset, const char* fmt, PropertyName* name) {
gc::AutoSuppressGC suppress(cx_);
if (UniqueChars bytes = AtomToPrintableString(cx_, name)) {
failfOffset(offset, fmt, bytes.get());
}
return false;
}
bool failName(ParseNode* pn, const char* fmt, PropertyName* name) {
return failNameOffset(pn->pn_pos.begin, fmt, name);
}
bool failOverRecursed() {
errorOverRecursed_ = true;
return false;
}
unsigned numArrayViews() const { return arrayViews_.length(); }
const ArrayView& arrayView(unsigned i) const { return arrayViews_[i]; }
unsigned numFuncDefs() const { return funcDefs_.length(); }
const Func& funcDef(unsigned i) const { return funcDefs_[i]; }
unsigned numFuncPtrTables() const { return tables_.length(); }
Table& table(unsigned i) const { return *tables_[i]; }
const Global* lookupGlobal(PropertyName* name) const {
if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
return p->value();
}
return nullptr;
}
Func* lookupFuncDef(PropertyName* name) {
if (GlobalMap::Ptr p = globalMap_.lookup(name)) {
Global* value = p->value();
if (value->which() == Global::Function) {
return &funcDefs_[value->funcDefIndex()];
}
}
return nullptr;
}
bool lookupStandardLibraryMathName(PropertyName* name,
MathBuiltin* mathBuiltin) const {
if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) {
*mathBuiltin = p->value();
return true;
}
return false;
}
bool startFunctionBodies() {
if (!arrayViews_.empty()) {
env_.memoryUsage = MemoryUsage::Unshared;
} else {
env_.memoryUsage = MemoryUsage::None;
}
return true;
}
};
template <typename Unit>
class MOZ_STACK_CLASS JS_HAZ_ROOTED ModuleValidator
: public ModuleValidatorShared {
private:
AsmJSParser<Unit>& parser_;
public:
ModuleValidator(JSContext* cx, AsmJSParser<Unit>& parser,
FunctionNode* moduleFunctionNode)
: ModuleValidatorShared(cx, moduleFunctionNode), parser_(parser) {}
~ModuleValidator() {
if (errorString_) {
MOZ_ASSERT(errorOffset_ != UINT32_MAX);
typeFailure(errorOffset_, errorString_.get());
}
if (errorOverRecursed_) {
ReportOverRecursed(cx_);
}
}
private:
bool newSig(FuncType&& sig, uint32_t* sigIndex) {
if (env_.types.length() >= MaxTypes) {
return failCurrentOffset("too many signatures");
}
*sigIndex = env_.types.length();
return env_.types.append(std::move(sig));
}
bool declareSig(FuncType&& sig, uint32_t* sigIndex) {
SigSet::AddPtr p = sigSet_.lookupForAdd(sig);
if (p) {
*sigIndex = p->sigIndex();
MOZ_ASSERT(env_.types[*sigIndex].funcType() == sig);
return true;
}
return newSig(std::move(sig), sigIndex) &&
sigSet_.add(p, HashableSig(*sigIndex, env_.types));
}
private:
void typeFailure(uint32_t offset, ...) {
va_list args;
va_start(args, offset);
auto& ts = tokenStream();
ErrorMetadata metadata;
if (ts.computeErrorMetadata(&metadata, AsVariant(offset))) {
if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailureOption) {
ReportCompileError(cx_, std::move(metadata), nullptr, JSREPORT_ERROR,
JSMSG_USE_ASM_TYPE_FAIL, &args);
} else {
Unused << ts.compileWarning(std::move(metadata), nullptr,
JSREPORT_WARNING, JSMSG_USE_ASM_TYPE_FAIL,
&args);
}
}
va_end(args);
}
public:
bool init() {
asmJSMetadata_ = cx_->new_<AsmJSMetadata>();
if (!asmJSMetadata_) {
return false;
}
asmJSMetadata_->toStringStart =
moduleFunctionNode_->funbox()->toStringStart;
asmJSMetadata_->srcStart = moduleFunctionNode_->body()->pn_pos.begin;
asmJSMetadata_->strict = parser_.pc_->sc()->strict() &&
!parser_.pc_->sc()->hasExplicitUseStrict();
asmJSMetadata_->scriptSource.reset(parser_.ss);
if (!addStandardLibraryMathInfo()) {
return false;
}
if (!initDummyFunction()) {
return false;
}
return true;
}
AsmJSParser<Unit>& parser() const { return parser_; }
auto& tokenStream() const { return parser_.tokenStream; }
public:
bool addFuncDef(PropertyName* name, uint32_t firstUse, FuncType&& sig,
Func** func) {
uint32_t sigIndex;
if (!declareSig(std::move(sig), &sigIndex)) {
return false;
}
uint32_t funcDefIndex = funcDefs_.length();
if (funcDefIndex >= MaxFuncs) {
return failCurrentOffset("too many functions");
}
Global* global = validationLifo_.new_<Global>(Global::Function);
if (!global) {
return false;
}
new (&global->u.funcDefIndex_) uint32_t(funcDefIndex);
if (!globalMap_.putNew(name, global)) {
return false;
}
if (!funcDefs_.emplaceBack(name, sigIndex, firstUse, funcDefIndex)) {
return false;
}
*func = &funcDefs_.back();
return true;
}
bool declareFuncPtrTable(FuncType&& sig, PropertyName* name,
uint32_t firstUse, uint32_t mask,
uint32_t* tableIndex) {
if (mask > MaxTableInitialLength) {
return failCurrentOffset("function pointer table too big");
}
MOZ_ASSERT(env_.tables.length() == tables_.length());
*tableIndex = env_.tables.length();
uint32_t sigIndex;
if (!newSig(std::move(sig), &sigIndex)) {
return false;
}
MOZ_ASSERT(sigIndex >= env_.asmJSSigToTableIndex.length());
if (!env_.asmJSSigToTableIndex.resize(sigIndex + 1)) {
return false;
}
env_.asmJSSigToTableIndex[sigIndex] = env_.tables.length();
if (!env_.tables.emplaceBack(TableKind::TypedFunction, Limits(mask + 1))) {
return false;
}
Global* global = validationLifo_.new_<Global>(Global::Table);
if (!global) {
return false;
}
new (&global->u.tableIndex_) uint32_t(*tableIndex);
if (!globalMap_.putNew(name, global)) {
return false;
}
Table* t = validationLifo_.new_<Table>(sigIndex, name, firstUse, mask);
return t && tables_.append(t);
}
bool declareImport(PropertyName* name, FuncType&& sig, unsigned ffiIndex,
uint32_t* importIndex) {
FuncImportMap::AddPtr p =
funcImportMap_.lookupForAdd(NamedSig::Lookup(name, sig));
if (p) {
*importIndex = p->value();
return true;
}
*importIndex = funcImportMap_.count();
MOZ_ASSERT(*importIndex == asmJSMetadata_->asmJSImports.length());
if (*importIndex >= MaxImports) {
return failCurrentOffset("too many imports");
}
if (!asmJSMetadata_->asmJSImports.emplaceBack(ffiIndex)) {
return false;
}
uint32_t sigIndex;
if (!declareSig(std::move(sig), &sigIndex)) {
return false;
}
return funcImportMap_.add(p, NamedSig(name, sigIndex, env_.types),
*importIndex);
}
bool failCurrentOffset(const char* str) {
return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin,
str);
}
SharedModule finish() {
MOZ_ASSERT(env_.funcTypes.empty());
if (!env_.funcTypes.resize(funcImportMap_.count() + funcDefs_.length())) {
return nullptr;
}
for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty();
r.popFront()) {
uint32_t funcIndex = r.front().value();
MOZ_ASSERT(!env_.funcTypes[funcIndex]);
env_.funcTypes[funcIndex] =
&env_.types[r.front().key().sigIndex()].funcType();
}
for (const Func& func : funcDefs_) {
uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex();
MOZ_ASSERT(!env_.funcTypes[funcIndex]);
env_.funcTypes[funcIndex] = &env_.types[func.sigIndex()].funcType();
}
if (!env_.funcImportGlobalDataOffsets.resize(funcImportMap_.count())) {
return nullptr;
}
MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty());
if (!asmJSMetadata_->asmJSFuncNames.resize(funcImportMap_.count())) {
return nullptr;
}
for (const Func& func : funcDefs_) {
CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func.name());
if (!funcName ||
!asmJSMetadata_->asmJSFuncNames.emplaceBack(std::move(funcName))) {
return nullptr;
}
}
uint32_t endBeforeCurly =
tokenStream().anyCharsAccess().currentToken().pos.end;
asmJSMetadata_->srcLength = endBeforeCurly - asmJSMetadata_->srcStart;
TokenPos pos;
MOZ_ALWAYS_TRUE(
tokenStream().peekTokenPos(&pos, TokenStreamShared::Operand));
uint32_t endAfterCurly = pos.end;
asmJSMetadata_->srcLengthWithRightBrace =
endAfterCurly - asmJSMetadata_->srcStart;
ScriptedCaller scriptedCaller;
if (parser_.ss->filename()) {
scriptedCaller.line = 0; scriptedCaller.filename = DuplicateString(parser_.ss->filename());
if (!scriptedCaller.filename) {
return nullptr;
}
}
SharedCompileArgs args = CompileArgs::build(cx_, std::move(scriptedCaller));
if (!args) {
return nullptr;
}
uint32_t codeSectionSize = 0;
for (const Func& func : funcDefs_) {
codeSectionSize += func.bytes().length();
}
env_.codeSection.emplace();
env_.codeSection->start = 0;
env_.codeSection->size = codeSectionSize;
SharedBytes bytes = cx_->new_<ShareableBytes>();
if (!bytes) {
return nullptr;
}
ModuleGenerator mg(*args, &env_, nullptr, nullptr);
if (!mg.init(asmJSMetadata_.get())) {
return nullptr;
}
for (Func& func : funcDefs_) {
if (!mg.compileFuncDef(funcImportMap_.count() + func.funcDefIndex(),
func.line(), func.bytes().begin(),
func.bytes().end(),
std::move(func.callSiteLineNums()))) {
return nullptr;
}
}
if (!mg.finishFuncDefs()) {
return nullptr;
}
return mg.finishModule(*bytes);
}
};
static bool IsNumericNonFloatLiteral(ParseNode* pn) {
return pn->isKind(ParseNodeKind::NumberExpr) ||
(pn->isKind(ParseNodeKind::NegExpr) &&
UnaryKid(pn)->isKind(ParseNodeKind::NumberExpr));
}
static bool IsCallToGlobal(ModuleValidatorShared& m, ParseNode* pn,
const ModuleValidatorShared::Global** global) {
if (!pn->isKind(ParseNodeKind::CallExpr)) {
return false;
}
ParseNode* callee = CallCallee(pn);
if (!callee->isKind(ParseNodeKind::Name)) {
return false;
}
*global = m.lookupGlobal(callee->as<NameNode>().name());
return !!*global;
}
static bool IsCoercionCall(ModuleValidatorShared& m, ParseNode* pn,
Type* coerceTo, ParseNode** coercedExpr) {
const ModuleValidatorShared::Global* global;
if (!IsCallToGlobal(m, pn, &global)) {
return false;
}
if (CallArgListLength(pn) != 1) {
return false;
}
if (coercedExpr) {
*coercedExpr = CallArgList(pn);
}
if (global->isMathFunction() &&
global->mathBuiltinFunction() == AsmJSMathBuiltin_fround) {
*coerceTo = Type::Float;
return true;
}
return false;
}
static bool IsFloatLiteral(ModuleValidatorShared& m, ParseNode* pn) {
ParseNode* coercedExpr;
Type coerceTo;
if (!IsCoercionCall(m, pn, &coerceTo, &coercedExpr)) {
return false;
}
if (!coerceTo.isFloat()) {
return false;
}
return IsNumericNonFloatLiteral(coercedExpr);
}
static bool IsNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) {
return IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn);
}
static double ExtractNumericNonFloatValue(ParseNode* pn,
ParseNode** out = nullptr) {
MOZ_ASSERT(IsNumericNonFloatLiteral(pn));
if (pn->isKind(ParseNodeKind::NegExpr)) {
pn = UnaryKid(pn);
if (out) {
*out = pn;
}
return -NumberNodeValue(pn);
}
return NumberNodeValue(pn);
}
static NumLit ExtractNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) {
MOZ_ASSERT(IsNumericLiteral(m, pn));
if (pn->isKind(ParseNodeKind::CallExpr)) {
MOZ_ASSERT(CallArgListLength(pn) == 1);
pn = CallArgList(pn);
double d = ExtractNumericNonFloatValue(pn);
return NumLit(NumLit::Float, DoubleValue(d));
}
double d = ExtractNumericNonFloatValue(pn, &pn);
if (NumberNodeHasFrac(pn) || IsNegativeZero(d)) {
return NumLit(NumLit::Double, DoubleValue(d));
}
MOZ_ASSERT(!IsNegativeZero(d));
MOZ_ASSERT(!IsNaN(d));
if (d < double(INT32_MIN) || d > double(UINT32_MAX)) {
return NumLit(NumLit::OutOfRangeInt, UndefinedValue());
}
int64_t i64 = int64_t(d);
if (i64 >= 0) {
if (i64 <= INT32_MAX) {
return NumLit(NumLit::Fixnum, Int32Value(i64));
}
MOZ_ASSERT(i64 <= UINT32_MAX);
return NumLit(NumLit::BigUnsigned, Int32Value(uint32_t(i64)));
}
MOZ_ASSERT(i64 >= INT32_MIN);
return NumLit(NumLit::NegativeInt, Int32Value(i64));
}
static inline bool IsLiteralInt(const NumLit& lit, uint32_t* u32) {
switch (lit.which()) {
case NumLit::Fixnum:
case NumLit::BigUnsigned:
case NumLit::NegativeInt:
*u32 = lit.toUint32();
return true;
case NumLit::Double:
case NumLit::Float:
case NumLit::OutOfRangeInt:
return false;
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal type");
}
static inline bool IsLiteralInt(ModuleValidatorShared& m, ParseNode* pn,
uint32_t* u32) {
return IsNumericLiteral(m, pn) &&
IsLiteralInt(ExtractNumericLiteral(m, pn), u32);
}
namespace {
typedef Vector<PropertyName*, 4, SystemAllocPolicy> LabelVector;
class MOZ_STACK_CLASS FunctionValidatorShared {
public:
struct Local {
Type type;
unsigned slot;
Local(Type t, unsigned slot) : type(t), slot(slot) {
MOZ_ASSERT(type.isCanonicalValType());
}
};
protected:
using LocalMap = HashMap<PropertyName*, Local>;
using LabelMap = HashMap<PropertyName*, uint32_t>;
ModuleValidatorShared& m_;
ParseNode* fn_;
Bytes bytes_;
Encoder encoder_;
Uint32Vector callSiteLineNums_;
LocalMap locals_;
LabelMap breakLabels_;
LabelMap continueLabels_;
Uint32Vector breakableStack_;
Uint32Vector continuableStack_;
uint32_t blockDepth_;
bool hasAlreadyReturned_;
ExprType ret_;
private:
FunctionValidatorShared(ModuleValidatorShared& m, ParseNode* fn,
JSContext* cx)
: m_(m),
fn_(fn),
encoder_(bytes_),
locals_(cx),
breakLabels_(cx),
continueLabels_(cx),
blockDepth_(0),
hasAlreadyReturned_(false),
ret_(ExprType::Limit) {}
protected:
template <typename Unit>
FunctionValidatorShared(ModuleValidator<Unit>& m, ParseNode* fn,
JSContext* cx)
: FunctionValidatorShared(static_cast<ModuleValidatorShared&>(m), fn,
cx) {}
public:
ModuleValidatorShared& m() const { return m_; }
JSContext* cx() const { return m_.cx(); }
ParseNode* fn() const { return fn_; }
void define(ModuleValidatorShared::Func* func, unsigned line) {
MOZ_ASSERT(!blockDepth_);
MOZ_ASSERT(breakableStack_.empty());
MOZ_ASSERT(continuableStack_.empty());
MOZ_ASSERT(breakLabels_.empty());
MOZ_ASSERT(continueLabels_.empty());
func->define(fn_, line, std::move(bytes_), std::move(callSiteLineNums_));
}
bool fail(ParseNode* pn, const char* str) { return m_.fail(pn, str); }
bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) {
va_list ap;
va_start(ap, fmt);
m_.failfVAOffset(pn->pn_pos.begin, fmt, ap);
va_end(ap);
return false;
}
bool failName(ParseNode* pn, const char* fmt, PropertyName* name) {
return m_.failName(pn, fmt, name);
}
bool addLocal(ParseNode* pn, PropertyName* name, Type type) {
LocalMap::AddPtr p = locals_.lookupForAdd(name);
if (p) {
return failName(pn, "duplicate local name '%s' not allowed", name);
}
return locals_.add(p, name, Local(type, locals_.count()));
}
bool hasAlreadyReturned() const { return hasAlreadyReturned_; }
ExprType returnedType() const { return ret_; }
void setReturnedType(ExprType ret) {
ret_ = ret;
hasAlreadyReturned_ = true;
}
private:
bool writeBr(uint32_t absolute, Op op = Op::Br) {
MOZ_ASSERT(op == Op::Br || op == Op::BrIf);
MOZ_ASSERT(absolute < blockDepth_);
return encoder().writeOp(op) &&
encoder().writeVarU32(blockDepth_ - 1 - absolute);
}
void removeLabel(PropertyName* label, LabelMap* map) {
LabelMap::Ptr p = map->lookup(label);
MOZ_ASSERT(p);
map->remove(p);
}
public:
bool pushBreakableBlock() {
return encoder().writeOp(Op::Block) &&
encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
breakableStack_.append(blockDepth_++);
}
bool popBreakableBlock() {
MOZ_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
return encoder().writeOp(Op::End);
}
bool pushUnbreakableBlock(const LabelVector* labels = nullptr) {
if (labels) {
for (PropertyName* label : *labels) {
if (!breakLabels_.putNew(label, blockDepth_)) {
return false;
}
}
}
blockDepth_++;
return encoder().writeOp(Op::Block) &&
encoder().writeFixedU8(uint8_t(ExprType::Void));
}
bool popUnbreakableBlock(const LabelVector* labels = nullptr) {
if (labels) {
for (PropertyName* label : *labels) {
removeLabel(label, &breakLabels_);
}
}
--blockDepth_;
return encoder().writeOp(Op::End);
}
bool pushContinuableBlock() {
return encoder().writeOp(Op::Block) &&
encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
continuableStack_.append(blockDepth_++);
}
bool popContinuableBlock() {
MOZ_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
return encoder().writeOp(Op::End);
}
bool pushLoop() {
return encoder().writeOp(Op::Block) &&
encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
encoder().writeOp(Op::Loop) &&
encoder().writeFixedU8(uint8_t(ExprType::Void)) &&
breakableStack_.append(blockDepth_++) &&
continuableStack_.append(blockDepth_++);
}
bool popLoop() {
MOZ_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_);
MOZ_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_);
return encoder().writeOp(Op::End) && encoder().writeOp(Op::End);
}
bool pushIf(size_t* typeAt) {
++blockDepth_;
return encoder().writeOp(Op::If) && encoder().writePatchableFixedU7(typeAt);
}
bool switchToElse() {
MOZ_ASSERT(blockDepth_ > 0);
return encoder().writeOp(Op::Else);
}
void setIfType(size_t typeAt, ExprType type) {
encoder().patchFixedU7(typeAt, uint8_t(type.code()));
}
bool popIf() {
MOZ_ASSERT(blockDepth_ > 0);
--blockDepth_;
return encoder().writeOp(Op::End);
}
bool popIf(size_t typeAt, ExprType type) {
MOZ_ASSERT(blockDepth_ > 0);
--blockDepth_;
if (!encoder().writeOp(Op::End)) {
return false;
}
setIfType(typeAt, type);
return true;
}
bool writeBreakIf() { return writeBr(breakableStack_.back(), Op::BrIf); }
bool writeContinueIf() { return writeBr(continuableStack_.back(), Op::BrIf); }
bool writeUnlabeledBreakOrContinue(bool isBreak) {
return writeBr(isBreak ? breakableStack_.back() : continuableStack_.back());
}
bool writeContinue() { return writeBr(continuableStack_.back()); }
bool addLabels(const LabelVector& labels, uint32_t relativeBreakDepth,
uint32_t relativeContinueDepth) {
for (PropertyName* label : labels) {
if (!breakLabels_.putNew(label, blockDepth_ + relativeBreakDepth)) {
return false;
}
if (!continueLabels_.putNew(label, blockDepth_ + relativeContinueDepth)) {
return false;
}
}
return true;
}
void removeLabels(const LabelVector& labels) {
for (PropertyName* label : labels) {
removeLabel(label, &breakLabels_);
removeLabel(label, &continueLabels_);
}
}
bool writeLabeledBreakOrContinue(PropertyName* label, bool isBreak) {
LabelMap& map = isBreak ? breakLabels_ : continueLabels_;
if (LabelMap::Ptr p = map.lookup(label)) {
return writeBr(p->value());
}
MOZ_CRASH("nonexistent label");
}
const Local* lookupLocal(PropertyName* name) const {
if (auto p = locals_.lookup(name)) {
return &p->value();
}
return nullptr;
}
const ModuleValidatorShared::Global* lookupGlobal(PropertyName* name) const {
if (locals_.has(name)) {
return nullptr;
}
return m_.lookupGlobal(name);
}
size_t numLocals() const { return locals_.count(); }
Encoder& encoder() { return encoder_; }
MOZ_MUST_USE bool writeInt32Lit(int32_t i32) {
return encoder().writeOp(Op::I32Const) && encoder().writeVarS32(i32);
}
MOZ_MUST_USE bool writeConstExpr(const NumLit& lit) {
switch (lit.which()) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return writeInt32Lit(lit.toInt32());
case NumLit::Float:
return encoder().writeOp(Op::F32Const) &&
encoder().writeFixedF32(lit.toFloat());
case NumLit::Double:
return encoder().writeOp(Op::F64Const) &&
encoder().writeFixedF64(lit.toDouble());
case NumLit::OutOfRangeInt:
break;
}
MOZ_CRASH("unexpected literal type");
}
};
template <typename Unit>
class MOZ_STACK_CLASS FunctionValidator : public FunctionValidatorShared {
public:
FunctionValidator(ModuleValidator<Unit>& m, ParseNode* fn)
: FunctionValidatorShared(m, fn, m.cx()) {}
public:
ModuleValidator<Unit>& m() const {
return static_cast<ModuleValidator<Unit>&>(FunctionValidatorShared::m());
}
MOZ_MUST_USE bool writeCall(ParseNode* pn, Op op) {
if (!encoder().writeOp(op)) {
return false;
}
return appendCallSiteLineNumber(pn);
}
MOZ_MUST_USE bool writeCall(ParseNode* pn, MozOp op) {
if (!encoder().writeOp(op)) {
return false;
}
return appendCallSiteLineNumber(pn);
}
MOZ_MUST_USE bool prepareCall(ParseNode* pn) {
return appendCallSiteLineNumber(pn);
}
private:
MOZ_MUST_USE bool appendCallSiteLineNumber(ParseNode* node) {
const TokenStreamAnyChars& anyChars = m().tokenStream().anyCharsAccess();
auto lineToken = anyChars.lineToken(node->pn_pos.begin);
return callSiteLineNums_.append(anyChars.lineNumber(lineToken));
}
};
}
static bool CheckIdentifier(ModuleValidatorShared& m, ParseNode* usepn,
PropertyName* name) {
if (name == m.cx()->names().arguments || name == m.cx()->names().eval) {
return m.failName(usepn, "'%s' is not an allowed identifier", name);
}
return true;
}
static bool CheckModuleLevelName(ModuleValidatorShared& m, ParseNode* usepn,
PropertyName* name) {
if (!CheckIdentifier(m, usepn, name)) {
return false;
}
if (name == m.moduleFunctionName() || name == m.globalArgumentName() ||
name == m.importArgumentName() || name == m.bufferArgumentName() ||
m.lookupGlobal(name)) {
return m.failName(usepn, "duplicate name '%s' not allowed", name);
}
return true;
}
static bool CheckFunctionHead(ModuleValidatorShared& m, FunctionNode* funNode) {
FunctionBox* funbox = funNode->funbox();
MOZ_ASSERT(!funbox->hasExprBody());
if (funbox->hasRest()) {
return m.fail(funNode, "rest args not allowed");
}
if (funbox->hasDestructuringArgs) {
return m.fail(funNode, "destructuring args not allowed");
}
return true;
}
static bool CheckArgument(ModuleValidatorShared& m, ParseNode* arg,
PropertyName** name) {
*name = nullptr;
if (!arg->isKind(ParseNodeKind::Name)) {
return m.fail(arg, "argument is not a plain name");
}
PropertyName* argName = arg->as<NameNode>().name();
;
if (!CheckIdentifier(m, arg, argName)) {
return false;
}
*name = argName;
return true;
}
static bool CheckModuleArgument(ModuleValidatorShared& m, ParseNode* arg,
PropertyName** name) {
if (!CheckArgument(m, arg, name)) {
return false;
}
if (!CheckModuleLevelName(m, arg, *name)) {
return false;
}
return true;
}
static bool CheckModuleArguments(ModuleValidatorShared& m,
FunctionNode* funNode) {
unsigned numFormals;
ParseNode* arg1 = FunctionFormalParametersList(funNode, &numFormals);
ParseNode* arg2 = arg1 ? NextNode(arg1) : nullptr;
ParseNode* arg3 = arg2 ? NextNode(arg2) : nullptr;
if (numFormals > 3) {
return m.fail(funNode, "asm.js modules takes at most 3 argument");
}
PropertyName* arg1Name = nullptr;
if (arg1 && !CheckModuleArgument(m, arg1, &arg1Name)) {
return false;
}
if (!m.initGlobalArgumentName(arg1Name)) {
return false;
}
PropertyName* arg2Name = nullptr;
if (arg2 && !CheckModuleArgument(m, arg2, &arg2Name)) {
return false;
}
if (!m.initImportArgumentName(arg2Name)) {
return false;
}
PropertyName* arg3Name = nullptr;
if (arg3 && !CheckModuleArgument(m, arg3, &arg3Name)) {
return false;
}
if (!m.initBufferArgumentName(arg3Name)) {
return false;
}
return true;
}
static bool CheckPrecedingStatements(ModuleValidatorShared& m,
ParseNode* stmtList) {
MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
ParseNode* stmt = ListHead(stmtList);
for (unsigned i = 0, n = ListLength(stmtList); i < n; i++) {
if (!IsIgnoredDirective(m.cx(), stmt)) {
return m.fail(stmt, "invalid asm.js statement");
}
}
return true;
}
static bool CheckGlobalVariableInitConstant(ModuleValidatorShared& m,
PropertyName* varName,
ParseNode* initNode, bool isConst) {
NumLit lit = ExtractNumericLiteral(m, initNode);
if (!lit.valid()) {
return m.fail(initNode,
"global initializer is out of representable integer range");
}
Type canonicalType = Type::canonicalize(Type::lit(lit));
if (!canonicalType.isGlobalVarType()) {
return m.fail(initNode, "global variable type not allowed");
}
return m.addGlobalVarInit(varName, lit, canonicalType, isConst);
}
static bool CheckTypeAnnotation(ModuleValidatorShared& m,
ParseNode* coercionNode, Type* coerceTo,
ParseNode** coercedExpr = nullptr) {
switch (coercionNode->getKind()) {
case ParseNodeKind::BitOrExpr: {
ParseNode* rhs = BitwiseRight(coercionNode);
uint32_t i;
if (!IsLiteralInt(m, rhs, &i) || i != 0) {
return m.fail(rhs, "must use |0 for argument/return coercion");
}
*coerceTo = Type::Int;
if (coercedExpr) {
*coercedExpr = BitwiseLeft(coercionNode);
}
return true;
}
case ParseNodeKind::PosExpr: {
*coerceTo = Type::Double;
if (coercedExpr) {
*coercedExpr = UnaryKid(coercionNode);
}
return true;
}
case ParseNodeKind::CallExpr: {
if (IsCoercionCall(m, coercionNode, coerceTo, coercedExpr)) {
return true;
}
break;
}
default:;
}
return m.fail(coercionNode, "must be of the form +x, x|0 or fround(x)");
}
static bool CheckGlobalVariableInitImport(ModuleValidatorShared& m,
PropertyName* varName,
ParseNode* initNode, bool isConst) {
Type coerceTo;
ParseNode* coercedExpr;
if (!CheckTypeAnnotation(m, initNode, &coerceTo, &coercedExpr)) {
return false;
}
if (!coercedExpr->isKind(ParseNodeKind::DotExpr)) {
return m.failName(coercedExpr, "invalid import expression for global '%s'",
varName);
}
if (!coerceTo.isGlobalVarType()) {
return m.fail(initNode, "global variable type not allowed");
}
ParseNode* base = DotBase(coercedExpr);
PropertyName* field = DotMember(coercedExpr);
PropertyName* importName = m.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, coerceTo, isConst);
}
static bool IsArrayViewCtorName(ModuleValidatorShared& m, PropertyName* name,
Scalar::Type* type) {
JSAtomState& names = m.cx()->names();
if (name == names.Int8Array) {
*type = Scalar::Int8;
} else if (name == names.Uint8Array) {
*type = Scalar::Uint8;
} else if (name == names.Int16Array) {
*type = Scalar::Int16;
} else if (name == names.Uint16Array) {
*type = Scalar::Uint16;
} else if (name == names.Int32Array) {
*type = Scalar::Int32;
} else if (name == names.Uint32Array) {
*type = Scalar::Uint32;
} else if (name == names.Float32Array) {
*type = Scalar::Float32;
} else if (name == names.Float64Array) {
*type = Scalar::Float64;
} else {
return false;
}
return true;
}
static bool CheckNewArrayViewArgs(ModuleValidatorShared& m, ParseNode* newExpr,
PropertyName* bufferName) {
ParseNode* ctorExpr = BinaryLeft(newExpr);
ParseNode* ctorArgs = BinaryRight(newExpr);
ParseNode* bufArg = ListHead(ctorArgs);
if (!bufArg || NextNode(bufArg) != nullptr) {
return m.fail(ctorExpr,
"array view constructor takes exactly one argument");
}
if (!IsUseOfName(bufArg, bufferName)) {
return m.failName(bufArg, "argument to array view constructor must be '%s'",
bufferName);
}
return true;
}
static bool CheckNewArrayView(ModuleValidatorShared& m, PropertyName* varName,
ParseNode* newExpr) {
PropertyName* globalName = m.globalArgumentName();
if (!globalName) {
return m.fail(
newExpr, "cannot create array view without an asm.js global parameter");
}
PropertyName* bufferName = m.bufferArgumentName();
if (!bufferName) {
return m.fail(newExpr,
"cannot create array view without an asm.js heap parameter");
}
ParseNode* ctorExpr = BinaryLeft(newExpr);
PropertyName* field;
Scalar::Type type;
if (ctorExpr->isKind(ParseNodeKind::DotExpr)) {
ParseNode* base = DotBase(ctorExpr);
if (!IsUseOfName(base, globalName)) {
return m.failName(base, "expecting '%s.*Array", globalName);
}
field = DotMember(ctorExpr);
if (!IsArrayViewCtorName(m, field, &type)) {
return m.fail(ctorExpr, "could not match typed array name");
}
} else {
if (!ctorExpr->isKind(ParseNodeKind::Name)) {
return m.fail(ctorExpr,
"expecting name of imported array view constructor");
}
PropertyName* globalName = ctorExpr->as<NameNode>().name();
const ModuleValidatorShared::Global* global = m.lookupGlobal(globalName);
if (!global) {
return m.failName(ctorExpr, "%s not found in module global scope",
globalName);
}
if (global->which() != ModuleValidatorShared::Global::ArrayViewCtor) {
return m.failName(ctorExpr,
"%s must be an imported array view constructor",
globalName);
}
field = nullptr;
type = global->viewType();
}
if (!CheckNewArrayViewArgs(m, newExpr, bufferName)) {
return false;
}
return m.addArrayView(varName, type, field);
}
static bool CheckGlobalMathImport(ModuleValidatorShared& m, ParseNode* initNode,
PropertyName* varName, PropertyName* field) {
ModuleValidatorShared::MathBuiltin mathBuiltin;
if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) {
return m.failName(initNode, "'%s' is not a standard Math builtin", field);
}
switch (mathBuiltin.kind) {
case ModuleValidatorShared::MathBuiltin::Function:
return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field);
case ModuleValidatorShared::MathBuiltin::Constant:
return m.addMathBuiltinConstant(varName, mathBuiltin.u.cst, field);
default:
break;
}
MOZ_CRASH("unexpected or uninitialized math builtin type");
}
static bool CheckGlobalDotImport(ModuleValidatorShared& m,
PropertyName* varName, ParseNode* initNode) {
ParseNode* base = DotBase(initNode);
PropertyName* field = DotMember(initNode);
if (base->isKind(ParseNodeKind::DotExpr)) {
ParseNode* global = DotBase(base);
PropertyName* math = DotMember(base);
PropertyName* globalName = m.globalArgumentName();
if (!globalName) {
return m.fail(
base, "import statement requires the module have a stdlib parameter");
}
if (!IsUseOfName(global, globalName)) {
if (global->isKind(ParseNodeKind::DotExpr)) {
return m.failName(base,
"imports can have at most two dot accesses "
"(e.g. %s.Math.sin)",
globalName);
}
return m.failName(base, "expecting %s.*", globalName);
}
if (math == m.cx()->names().Math) {
return CheckGlobalMathImport(m, initNode, varName, field);
}
return m.failName(base, "expecting %s.Math", globalName);
}
if (!base->isKind(ParseNodeKind::Name)) {
return m.fail(base, "expected name of variable or parameter");
}
auto baseName = base->as<NameNode>().name();
if (baseName == m.globalArgumentName()) {
if (field == m.cx()->names().NaN) {
return m.addGlobalConstant(varName, GenericNaN(), field);
}
if (field == m.cx()->names().Infinity) {
return m.addGlobalConstant(varName, PositiveInfinity<double>(), field);
}
Scalar::Type type;
if (IsArrayViewCtorName(m, field, &type)) {
return m.addArrayViewCtor(varName, type, field);
}
return m.failName(
initNode, "'%s' is not a standard constant or typed array name", field);
}
if (baseName != m.importArgumentName()) {
return m.fail(base, "expected global or import name");
}
return m.addFFI(varName, field);
}
static bool CheckModuleGlobal(ModuleValidatorShared& m, ParseNode* decl,
bool isConst) {
if (!decl->isKind(ParseNodeKind::AssignExpr)) {
return m.fail(decl, "module import needs initializer");
}
AssignmentNode* assignNode = &decl->as<AssignmentNode>();
ParseNode* var = assignNode->left();
if (!var->isKind(ParseNodeKind::Name)) {
return m.fail(var, "import variable is not a plain name");
}
PropertyName* varName = var->as<NameNode>().name();
if (!CheckModuleLevelName(m, var, varName)) {
return false;
}
ParseNode* initNode = assignNode->right();
if (IsNumericLiteral(m, initNode)) {
return CheckGlobalVariableInitConstant(m, varName, initNode, isConst);
}
if (initNode->isKind(ParseNodeKind::BitOrExpr) ||
initNode->isKind(ParseNodeKind::PosExpr) ||
initNode->isKind(ParseNodeKind::CallExpr)) {
return CheckGlobalVariableInitImport(m, varName, initNode, isConst);
}
if (initNode->isKind(ParseNodeKind::NewExpr)) {
return CheckNewArrayView(m, varName, initNode);
}
if (initNode->isKind(ParseNodeKind::DotExpr)) {
return CheckGlobalDotImport(m, varName, initNode);
}
return m.fail(initNode, "unsupported import expression");
}
template <typename Unit>
static bool CheckModuleProcessingDirectives(ModuleValidator<Unit>& m) {
auto& ts = m.parser().tokenStream;
while (true) {
bool matched;
if (!ts.matchToken(&matched, TokenKind::String,
TokenStreamShared::Operand)) {
return false;
}
if (!matched) {
return true;
}
if (!IsIgnoredDirectiveName(m.cx(),
ts.anyCharsAccess().currentToken().atom())) {
return m.failCurrentOffset("unsupported processing directive");
}
TokenKind tt;
if (!ts.getToken(&tt)) {
return false;
}
if (tt != TokenKind::Semi) {
return m.failCurrentOffset("expected semicolon after string literal");
}
}
}
template <typename Unit>
static bool CheckModuleGlobals(ModuleValidator<Unit>& m) {
while (true) {
ParseNode* varStmt;
if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
return false;
}
if (!varStmt) {
break;
}
for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) {
if (!CheckModuleGlobal(m, var,
varStmt->isKind(ParseNodeKind::ConstDecl))) {
return false;
}
}
}
return true;
}
static bool ArgFail(FunctionValidatorShared& f, PropertyName* argName,
ParseNode* stmt) {
return f.failName(stmt,
"expecting argument type declaration for '%s' of the "
"form 'arg = arg|0' or 'arg = +arg' or 'arg = fround(arg)'",
argName);
}
static bool CheckArgumentType(FunctionValidatorShared& f, ParseNode* stmt,
PropertyName* name, Type* type) {
if (!stmt || !IsExpressionStatement(stmt)) {
return ArgFail(f, name, stmt ? stmt : f.fn());
}
ParseNode* initNode = ExpressionStatementExpr(stmt);
if (!initNode->isKind(ParseNodeKind::AssignExpr)) {
return ArgFail(f, name, stmt);
}
ParseNode* argNode = BinaryLeft(initNode);
ParseNode* coercionNode = BinaryRight(initNode);
if (!IsUseOfName(argNode, name)) {
return ArgFail(f, name, stmt);
}
ParseNode* coercedExpr;
if (!CheckTypeAnnotation(f.m(), coercionNode, type, &coercedExpr)) {
return false;
}
if (!type->isArgType()) {
return f.failName(stmt, "invalid type for argument '%s'", name);
}
if (!IsUseOfName(coercedExpr, name)) {
return ArgFail(f, name, stmt);
}
return true;
}
static bool CheckProcessingDirectives(ModuleValidatorShared& m,
ParseNode** stmtIter) {
ParseNode* stmt = *stmtIter;
while (stmt && IsIgnoredDirective(m.cx(), stmt)) {
stmt = NextNode(stmt);
}
*stmtIter = stmt;
return true;
}
static bool CheckArguments(FunctionValidatorShared& f, ParseNode** stmtIter,
ValTypeVector* argTypes) {
ParseNode* stmt = *stmtIter;
unsigned numFormals;
ParseNode* argpn = FunctionFormalParametersList(f.fn(), &numFormals);
for (unsigned i = 0; i < numFormals;
i++, argpn = NextNode(argpn), stmt = NextNode(stmt)) {
PropertyName* name;
if (!CheckArgument(f.m(), argpn, &name)) {
return false;
}
Type type;
if (!CheckArgumentType(f, stmt, name, &type)) {
return false;
}
if (!argTypes->append(type.canonicalToValType())) {
return false;
}
if (!f.addLocal(argpn, name, type)) {
return false;
}
}
*stmtIter = stmt;
return true;
}
static bool IsLiteralOrConst(FunctionValidatorShared& f, ParseNode* pn,
NumLit* lit) {
if (pn->isKind(ParseNodeKind::Name)) {
const ModuleValidatorShared::Global* global =
f.lookupGlobal(pn->as<NameNode>().name());
if (!global ||
global->which() != ModuleValidatorShared::Global::ConstantLiteral) {
return false;
}
*lit = global->constLiteralValue();
return true;
}
if (!IsNumericLiteral(f.m(), pn)) {
return false;
}
*lit = ExtractNumericLiteral(f.m(), pn);
return true;
}
static bool CheckFinalReturn(FunctionValidatorShared& f,
ParseNode* lastNonEmptyStmt) {
if (!f.encoder().writeOp(Op::End)) {
return false;
}
if (!f.hasAlreadyReturned()) {
f.setReturnedType(ExprType::Void);
return true;
}
if (!lastNonEmptyStmt->isKind(ParseNodeKind::ReturnStmt) &&
!IsVoid(f.returnedType())) {
return f.fail(lastNonEmptyStmt,
"void incompatible with previous return type");
}
return true;
}
static bool CheckVariable(FunctionValidatorShared& f, ParseNode* decl,
ValTypeVector* types, Vector<NumLit>* inits) {
if (!decl->isKind(ParseNodeKind::AssignExpr)) {
return f.failName(
decl, "var '%s' needs explicit type declaration via an initial value",
decl->as<NameNode>().name());
}
AssignmentNode* assignNode = &decl->as<AssignmentNode>();
ParseNode* var = assignNode->left();
if (!var->isKind(ParseNodeKind::Name)) {
return f.fail(var, "local variable is not a plain name");
}
PropertyName* name = var->as<NameNode>().name();
if (!CheckIdentifier(f.m(), var, name)) {
return false;
}
ParseNode* initNode = assignNode->right();
NumLit lit;
if (!IsLiteralOrConst(f, initNode, &lit)) {
return f.failName(
var, "var '%s' initializer must be literal or const literal", name);
}
if (!lit.valid()) {
return f.failName(var, "var '%s' initializer out of range", name);
}
Type type = Type::canonicalize(Type::lit(lit));
return f.addLocal(var, name, type) &&
types->append(type.canonicalToValType()) && inits->append(lit);
}
static bool CheckVariables(FunctionValidatorShared& f, ParseNode** stmtIter) {
ParseNode* stmt = *stmtIter;
uint32_t firstVar = f.numLocals();
ValTypeVector types;
Vector<NumLit> inits(f.cx());
for (; stmt && stmt->isKind(ParseNodeKind::VarStmt);
stmt = NextNonEmptyStatement(stmt)) {
for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) {
if (!CheckVariable(f, var, &types, &inits)) {
return false;
}
}
}
MOZ_ASSERT(f.encoder().empty());
if (!EncodeLocalEntries(f.encoder(), types)) {
return false;
}
for (uint32_t i = 0; i < inits.length(); i++) {
NumLit lit = inits[i];
if (lit.isZeroBits()) {
continue;
}
if (!f.writeConstExpr(lit)) {
return false;
}
if (!f.encoder().writeOp(Op::SetLocal)) {
return false;
}
if (!f.encoder().writeVarU32(firstVar + i)) {
return false;
}
}
*stmtIter = stmt;
return true;
}
template <typename Unit>
static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* op, Type* type);
template <typename Unit>
static bool CheckNumericLiteral(FunctionValidator<Unit>& f, ParseNode* num,
Type* type) {
NumLit lit = ExtractNumericLiteral(f.m(), num);
if (!lit.valid()) {
return f.fail(num, "numeric literal out of representable integer range");
}
*type = Type::lit(lit);
return f.writeConstExpr(lit);
}
static bool CheckVarRef(FunctionValidatorShared& f, ParseNode* varRef,
Type* type) {
PropertyName* name = varRef->as<NameNode>().name();
if (const FunctionValidatorShared::Local* local = f.lookupLocal(name)) {
if (!f.encoder().writeOp(Op::GetLocal)) {
return false;
}
if (!f.encoder().writeVarU32(local->slot)) {
return false;
}
*type = local->type;
return true;
}
if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
switch (global->which()) {
case ModuleValidatorShared::Global::ConstantLiteral:
*type = global->varOrConstType();
return f.writeConstExpr(global->constLiteralValue());
case ModuleValidatorShared::Global::ConstantImport:
case ModuleValidatorShared::Global::Variable: {
*type = global->varOrConstType();
return f.encoder().writeOp(Op::GetGlobal) &&
f.encoder().writeVarU32(global->varOrConstIndex());
}
case ModuleValidatorShared::Global::Function:
case ModuleValidatorShared::Global::FFI:
case ModuleValidatorShared::Global::MathBuiltinFunction:
case ModuleValidatorShared::Global::Table:
case ModuleValidatorShared::Global::ArrayView:
case ModuleValidatorShared::Global::ArrayViewCtor:
break;
}
return f.failName(varRef,
"'%s' may not be accessed by ordinary expressions", name);
}
return f.failName(varRef, "'%s' not found in local or asm.js module scope",
name);
}
static inline bool IsLiteralOrConstInt(FunctionValidatorShared& f,
ParseNode* pn, uint32_t* u32) {
NumLit lit;
if (!IsLiteralOrConst(f, pn, &lit)) {
return false;
}
return IsLiteralInt(lit, u32);
}
static const int32_t NoMask = -1;
template <typename Unit>
static bool CheckArrayAccess(FunctionValidator<Unit>& f, ParseNode* viewName,
ParseNode* indexExpr, Scalar::Type* viewType) {
if (!viewName->isKind(ParseNodeKind::Name)) {
return f.fail(viewName,
"base of array access must be a typed array view name");
}
const ModuleValidatorShared::Global* global =
f.lookupGlobal(viewName->as<NameNode>().name());
if (!global || !global->isAnyArrayView()) {
return f.fail(viewName,
"base of array access must be a typed array view name");
}
*viewType = global->viewType();
uint32_t index;
if (IsLiteralOrConstInt(f, indexExpr, &index)) {
uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType);
uint64_t width = TypedArrayElemSize(*viewType);
if (!f.m().tryConstantAccess(byteOffset, width)) {
return f.fail(indexExpr, "constant index out of range");
}
return f.writeInt32Lit(byteOffset);
}
int32_t mask = ~(TypedArrayElemSize(*viewType) - 1);
if (indexExpr->isKind(ParseNodeKind::RshExpr)) {
ParseNode* shiftAmountNode = BitwiseRight(indexExpr);
uint32_t shift;
if (!IsLiteralInt(f.m(), shiftAmountNode, &shift)) {
return f.failf(shiftAmountNode, "shift amount must be constant");
}
unsigned requiredShift = TypedArrayShift(*viewType);
if (shift != requiredShift) {
return f.failf(shiftAmountNode, "shift amount must be %u", requiredShift);
}
ParseNode* pointerNode = BitwiseLeft(indexExpr);
Type pointerType;
if (!CheckExpr(f, pointerNode, &pointerType)) {
return false;
}
if (!pointerType.isIntish()) {
return f.failf(pointerNode, "%s is not a subtype of int",
pointerType.toChars());
}
} else {
if (TypedArrayShift(*viewType) != 0) {
return f.fail(
indexExpr,
"index expression isn't shifted; must be an Int8/Uint8 access");
}
MOZ_ASSERT(mask == NoMask);
ParseNode* pointerNode = indexExpr;
Type pointerType;
if (!CheckExpr(f, pointerNode, &pointerType)) {
return false;
}
if (!pointerType.isInt()) {
return f.failf(pointerNode, "%s is not a subtype of int",
pointerType.toChars());
}
}
if (mask != NoMask) {
return f.writeInt32Lit(mask) && f.encoder().writeOp(Op::I32And);
}
return true;
}
static bool WriteArrayAccessFlags(FunctionValidatorShared& f,
Scalar::Type viewType) {
size_t align = TypedArrayElemSize(viewType);
MOZ_ASSERT(IsPowerOfTwo(align));
if (!f.encoder().writeFixedU8(CeilingLog2(align))) {
return false;
}
if (!f.encoder().writeVarU32(0)) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckLoadArray(FunctionValidator<Unit>& f, ParseNode* elem,
Type* type) {
Scalar::Type viewType;
if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) {
return false;
}
switch (viewType) {
case Scalar::Int8:
if (!f.encoder().writeOp(Op::I32Load8S)) return false;
break;
case Scalar::Uint8:
if (!f.encoder().writeOp(Op::I32Load8U)) return false;
break;
case Scalar::Int16:
if (!f.encoder().writeOp(Op::I32Load16S)) return false;
break;
case Scalar::Uint16:
if (!f.encoder().writeOp(Op::I32Load16U)) return false;
break;
case Scalar::Uint32:
case Scalar::Int32:
if (!f.encoder().writeOp(Op::I32Load)) return false;
break;
case Scalar::Float32:
if (!f.encoder().writeOp(Op::F32Load)) return false;
break;
case Scalar::Float64:
if (!f.encoder().writeOp(Op::F64Load)) return false;
break;
default:
MOZ_CRASH("unexpected scalar type");
}
switch (viewType) {
case Scalar::Int8:
case Scalar::Int16:
case Scalar::Int32:
case Scalar::Uint8:
case Scalar::Uint16:
case Scalar::Uint32:
*type = Type::Intish;
break;
case Scalar::Float32:
*type = Type::MaybeFloat;
break;
case Scalar::Float64:
*type = Type::MaybeDouble;
break;
default:
MOZ_CRASH("Unexpected array type");
}
if (!WriteArrayAccessFlags(f, viewType)) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckStoreArray(FunctionValidator<Unit>& f, ParseNode* lhs,
ParseNode* rhs, Type* type) {
Scalar::Type viewType;
if (!CheckArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), &viewType)) {
return false;
}
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
switch (viewType) {
case Scalar::Int8:
case Scalar::Int16:
case Scalar::Int32:
case Scalar::Uint8:
case Scalar::Uint16:
case Scalar::Uint32:
if (!rhsType.isIntish()) {
return f.failf(lhs, "%s is not a subtype of intish", rhsType.toChars());
}
break;
case Scalar::Float32:
if (!rhsType.isMaybeDouble() && !rhsType.isFloatish()) {
return f.failf(lhs, "%s is not a subtype of double? or floatish",
rhsType.toChars());
}
break;
case Scalar::Float64:
if (!rhsType.isMaybeFloat() && !rhsType.isMaybeDouble()) {
return f.failf(lhs, "%s is not a subtype of float? or double?",
rhsType.toChars());
}
break;
default:
MOZ_CRASH("Unexpected view type");
}
switch (viewType) {
case Scalar::Int8:
case Scalar::Uint8:
if (!f.encoder().writeOp(MozOp::I32TeeStore8)) {
return false;
}
break;
case Scalar::Int16:
case Scalar::Uint16:
if (!f.encoder().writeOp(MozOp::I32TeeStore16)) {
return false;
}
break;
case Scalar::Int32:
case Scalar::Uint32:
if (!f.encoder().writeOp(MozOp::I32TeeStore)) {
return false;
}
break;
case Scalar::Float32:
if (rhsType.isFloatish()) {
if (!f.encoder().writeOp(MozOp::F32TeeStore)) {
return false;
}
} else {
if (!f.encoder().writeOp(MozOp::F64TeeStoreF32)) {
return false;
}
}
break;
case Scalar::Float64:
if (rhsType.isFloatish()) {
if (!f.encoder().writeOp(MozOp::F32TeeStoreF64)) {
return false;
}
} else {
if (!f.encoder().writeOp(MozOp::F64TeeStore)) {
return false;
}
}
break;
default:
MOZ_CRASH("unexpected scalar type");
}
if (!WriteArrayAccessFlags(f, viewType)) {
return false;
}
*type = rhsType;
return true;
}
template <typename Unit>
static bool CheckAssignName(FunctionValidator<Unit>& f, ParseNode* lhs,
ParseNode* rhs, Type* type) {
RootedPropertyName name(f.cx(), lhs->as<NameNode>().name());
if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) {
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (!f.encoder().writeOp(Op::TeeLocal)) {
return false;
}
if (!f.encoder().writeVarU32(lhsVar->slot)) {
return false;
}
if (!(rhsType <= lhsVar->type)) {
return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
lhsVar->type.toChars());
}
*type = rhsType;
return true;
}
if (const ModuleValidatorShared::Global* global = f.lookupGlobal(name)) {
if (global->which() != ModuleValidatorShared::Global::Variable) {
return f.failName(lhs, "'%s' is not a mutable variable", name);
}
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
Type globType = global->varOrConstType();
if (!(rhsType <= globType)) {
return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
globType.toChars());
}
if (!f.encoder().writeOp(MozOp::TeeGlobal)) {
return false;
}
if (!f.encoder().writeVarU32(global->varOrConstIndex())) {
return false;
}
*type = rhsType;
return true;
}
return f.failName(lhs, "'%s' not found in local or asm.js module scope",
name);
}
template <typename Unit>
static bool CheckAssign(FunctionValidator<Unit>& f, ParseNode* assign,
Type* type) {
MOZ_ASSERT(assign->isKind(ParseNodeKind::AssignExpr));
ParseNode* lhs = BinaryLeft(assign);
ParseNode* rhs = BinaryRight(assign);
if (lhs->getKind() == ParseNodeKind::ElemExpr) {
return CheckStoreArray(f, lhs, rhs, type);
}
if (lhs->getKind() == ParseNodeKind::Name) {
return CheckAssignName(f, lhs, rhs, type);
}
return f.fail(
assign,
"left-hand side of assignment must be a variable or array access");
}
template <typename Unit>
static bool CheckMathIMul(FunctionValidator<Unit>& f, ParseNode* call,
Type* type) {
if (CallArgListLength(call) != 2) {
return f.fail(call, "Math.imul must be passed 2 arguments");
}
ParseNode* lhs = CallArgList(call);
ParseNode* rhs = NextNode(lhs);
Type lhsType;
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (!lhsType.isIntish()) {
return f.failf(lhs, "%s is not a subtype of intish", lhsType.toChars());
}
if (!rhsType.isIntish()) {
return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
}
*type = Type::Signed;
return f.encoder().writeOp(Op::I32Mul);
}
template <typename Unit>
static bool CheckMathClz32(FunctionValidator<Unit>& f, ParseNode* call,
Type* type) {
if (CallArgListLength(call) != 1) {
return f.fail(call, "Math.clz32 must be passed 1 argument");
}
ParseNode* arg = CallArgList(call);
Type argType;
if (!CheckExpr(f, arg, &argType)) {
return false;
}
if (!argType.isIntish()) {
return f.failf(arg, "%s is not a subtype of intish", argType.toChars());
}
*type = Type::Fixnum;
return f.encoder().writeOp(Op::I32Clz);
}
template <typename Unit>
static bool CheckMathAbs(FunctionValidator<Unit>& f, ParseNode* call,
Type* type) {
if (CallArgListLength(call) != 1) {
return f.fail(call, "Math.abs must be passed 1 argument");
}
ParseNode* arg = CallArgList(call);
Type argType;
if (!CheckExpr(f, arg, &argType)) {
return false;
}
if (argType.isSigned()) {
*type = Type::Unsigned;
return f.encoder().writeOp(MozOp::I32Abs);
}
if (argType.isMaybeDouble()) {
*type = Type::Double;
return f.encoder().writeOp(Op::F64Abs);
}
if (argType.isMaybeFloat()) {
*type = Type::Floatish;
return f.encoder().writeOp(Op::F32Abs);
}
return f.failf(call, "%s is not a subtype of signed, float? or double?",
argType.toChars());
}
template <typename Unit>
static bool CheckMathSqrt(FunctionValidator<Unit>& f, ParseNode* call,
Type* type) {
if (CallArgListLength(call) != 1) {
return f.fail(call, "Math.sqrt must be passed 1 argument");
}
ParseNode* arg = CallArgList(call);
Type argType;
if (!CheckExpr(f, arg, &argType)) {
return false;
}
if (argType.isMaybeDouble()) {
*type = Type::Double;
return f.encoder().writeOp(Op::F64Sqrt);
}
if (argType.isMaybeFloat()) {
*type = Type::Floatish;
return f.encoder().writeOp(Op::F32Sqrt);
}
return f.failf(call, "%s is neither a subtype of double? nor float?",
argType.toChars());
}
template <typename Unit>
static bool CheckMathMinMax(FunctionValidator<Unit>& f, ParseNode* callNode,
bool isMax, Type* type) {
if (CallArgListLength(callNode) < 2) {
return f.fail(callNode, "Math.min/max must be passed at least 2 arguments");
}
ParseNode* firstArg = CallArgList(callNode);
Type firstType;
if (!CheckExpr(f, firstArg, &firstType)) {
return false;
}
Op op = Op::Limit;
MozOp mozOp = MozOp::Limit;
if (firstType.isMaybeDouble()) {
*type = Type::Double;
firstType = Type::MaybeDouble;
op = isMax ? Op::F64Max : Op::F64Min;
} else if (firstType.isMaybeFloat()) {
*type = Type::Float;
firstType = Type::MaybeFloat;
op = isMax ? Op::F32Max : Op::F32Min;
} else if (firstType.isSigned()) {
*type = Type::Signed;
firstType = Type::Signed;
mozOp = isMax ? MozOp::I32Max : MozOp::I32Min;
} else {
return f.failf(firstArg, "%s is not a subtype of double?, float? or signed",
firstType.toChars());
}
unsigned numArgs = CallArgListLength(callNode);
ParseNode* nextArg = NextNode(firstArg);
for (unsigned i = 1; i < numArgs; i++, nextArg = NextNode(nextArg)) {
Type nextType;
if (!CheckExpr(f, nextArg, &nextType)) {
return false;
}
if (!(nextType <= firstType)) {
return f.failf(nextArg, "%s is not a subtype of %s", nextType.toChars(),
firstType.toChars());
}
if (op != Op::Limit) {
if (!f.encoder().writeOp(op)) {
return false;
}
} else {
if (!f.encoder().writeOp(mozOp)) {
return false;
}
}
}
return true;
}
using CheckArgType = bool (*)(FunctionValidatorShared& f, ParseNode* argNode,
Type type);
template <CheckArgType checkArg, typename Unit>
static bool CheckCallArgs(FunctionValidator<Unit>& f, ParseNode* callNode,
ValTypeVector* args) {
ParseNode* argNode = CallArgList(callNode);
for (unsigned i = 0; i < CallArgListLength(callNode);
i++, argNode = NextNode(argNode)) {
Type type;
if (!CheckExpr(f, argNode, &type)) {
return false;
}
if (!checkArg(f, argNode, type)) {
return false;
}
if (!args->append(Type::canonicalize(type).canonicalToValType())) {
return false;
}
}
return true;
}
static bool CheckSignatureAgainstExisting(ModuleValidatorShared& m,
ParseNode* usepn, const FuncType& sig,
const FuncType& existing) {
if (sig.args().length() != existing.args().length()) {
return m.failf(usepn,
"incompatible number of arguments (%zu"
" here vs. %zu before)",
sig.args().length(), existing.args().length());
}
for (unsigned i = 0; i < sig.args().length(); i++) {
if (sig.arg(i) != existing.arg(i)) {
return m.failf(
usepn, "incompatible type for argument %u: (%s here vs. %s before)",
i, ToCString(sig.arg(i)), ToCString(existing.arg(i)));
}
}
if (sig.ret() != existing.ret()) {
return m.failf(usepn, "%s incompatible with previous return of type %s",
ToCString(sig.ret()), ToCString(existing.ret()));
}
MOZ_ASSERT(sig == existing);
return true;
}
template <typename Unit>
static bool CheckFunctionSignature(ModuleValidator<Unit>& m, ParseNode* usepn,
FuncType&& sig, PropertyName* name,
ModuleValidatorShared::Func** func) {
if (sig.args().length() > MaxParams) {
return m.failf(usepn, "too many parameters");
}
ModuleValidatorShared::Func* existing = m.lookupFuncDef(name);
if (!existing) {
if (!CheckModuleLevelName(m, usepn, name)) {
return false;
}
return m.addFuncDef(name, usepn->pn_pos.begin, std::move(sig), func);
}
const FuncTypeWithId& existingSig =
m.env().types[existing->sigIndex()].funcType();
if (!CheckSignatureAgainstExisting(m, usepn, sig, existingSig)) {
return false;
}
*func = existing;
return true;
}
static bool CheckIsArgType(FunctionValidatorShared& f, ParseNode* argNode,
Type type) {
if (!type.isArgType()) {
return f.failf(argNode, "%s is not a subtype of int, float, or double",
type.toChars());
}
return true;
}
template <typename Unit>
static bool CheckInternalCall(FunctionValidator<Unit>& f, ParseNode* callNode,
PropertyName* calleeName, Type ret, Type* type) {
MOZ_ASSERT(ret.isCanonical());
ValTypeVector args;
if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
return false;
}
FuncType sig(std::move(args), ret.canonicalToExprType());
ModuleValidatorShared::Func* callee;
if (!CheckFunctionSignature(f.m(), callNode, std::move(sig), calleeName,
&callee)) {
return false;
}
if (!f.writeCall(callNode, MozOp::OldCallDirect)) {
return false;
}
if (!f.encoder().writeVarU32(callee->funcDefIndex())) {
return false;
}
*type = Type::ret(ret);
return true;
}
template <typename Unit>
static bool CheckFuncPtrTableAgainstExisting(ModuleValidator<Unit>& m,
ParseNode* usepn,
PropertyName* name, FuncType&& sig,
unsigned mask,
uint32_t* tableIndex) {
if (const ModuleValidatorShared::Global* existing = m.lookupGlobal(name)) {
if (existing->which() != ModuleValidatorShared::Global::Table) {
return m.failName(usepn, "'%s' is not a function-pointer table", name);
}
ModuleValidatorShared::Table& table = m.table(existing->tableIndex());
if (mask != table.mask()) {
return m.failf(usepn, "mask does not match previous value (%u)",
table.mask());
}
if (!CheckSignatureAgainstExisting(
m, usepn, sig, m.env().types[table.sigIndex()].funcType())) {
return false;
}
*tableIndex = existing->tableIndex();
return true;
}
if (!CheckModuleLevelName(m, usepn, name)) {
return false;
}
if (!m.declareFuncPtrTable(std::move(sig), name, usepn->pn_pos.begin, mask,
tableIndex)) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckFuncPtrCall(FunctionValidator<Unit>& f, ParseNode* callNode,
Type ret, Type* type) {
MOZ_ASSERT(ret.isCanonical());
ParseNode* callee = CallCallee(callNode);
ParseNode* tableNode = ElemBase(callee);
ParseNode* indexExpr = ElemIndex(callee);
if (!tableNode->isKind(ParseNodeKind::Name)) {
return f.fail(tableNode, "expecting name of function-pointer array");
}
PropertyName* name = tableNode->as<NameNode>().name();
if (const ModuleValidatorShared::Global* existing = f.lookupGlobal(name)) {
if (existing->which() != ModuleValidatorShared::Global::Table) {
return f.failName(
tableNode, "'%s' is not the name of a function-pointer array", name);
}
}
if (!indexExpr->isKind(ParseNodeKind::BitAndExpr)) {
return f.fail(indexExpr,
"function-pointer table index expression needs & mask");
}
ParseNode* indexNode = BitwiseLeft(indexExpr);
ParseNode* maskNode = BitwiseRight(indexExpr);
uint32_t mask;
if (!IsLiteralInt(f.m(), maskNode, &mask) || mask == UINT32_MAX ||
!IsPowerOfTwo(mask + 1)) {
return f.fail(maskNode,
"function-pointer table index mask value must be a power of "
"two minus 1");
}
Type indexType;
if (!CheckExpr(f, indexNode, &indexType)) {
return false;
}
if (!indexType.isIntish()) {
return f.failf(indexNode, "%s is not a subtype of intish",
indexType.toChars());
}
ValTypeVector args;
if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) {
return false;
}
FuncType sig(std::move(args), ret.canonicalToExprType());
uint32_t tableIndex;
if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, std::move(sig),
mask, &tableIndex)) {
return false;
}
if (!f.writeCall(callNode, MozOp::OldCallIndirect)) {
return false;
}
if (!f.encoder().writeVarU32(f.m().table(tableIndex).sigIndex())) {
return false;
}
*type = Type::ret(ret);
return true;
}
static bool CheckIsExternType(FunctionValidatorShared& f, ParseNode* argNode,
Type type) {
if (!type.isExtern()) {
return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
}
return true;
}
template <typename Unit>
static bool CheckFFICall(FunctionValidator<Unit>& f, ParseNode* callNode,
unsigned ffiIndex, Type ret, Type* type) {
MOZ_ASSERT(ret.isCanonical());
PropertyName* calleeName = CallCallee(callNode)->as<NameNode>().name();
if (ret.isFloat()) {
return f.fail(callNode, "FFI calls can't return float");
}
ValTypeVector args;
if (!CheckCallArgs<CheckIsExternType>(f, callNode, &args)) {
return false;
}
FuncType sig(std::move(args), ret.canonicalToExprType());
uint32_t importIndex;
if (!f.m().declareImport(calleeName, std::move(sig), ffiIndex,
&importIndex)) {
return false;
}
if (!f.writeCall(callNode, Op::Call)) {
return false;
}
if (!f.encoder().writeVarU32(importIndex)) {
return false;
}
*type = Type::ret(ret);
return true;
}
static bool CheckFloatCoercionArg(FunctionValidatorShared& f,
ParseNode* inputNode, Type inputType) {
if (inputType.isMaybeDouble()) {
return f.encoder().writeOp(Op::F32DemoteF64);
}
if (inputType.isSigned()) {
return f.encoder().writeOp(Op::F32ConvertSI32);
}
if (inputType.isUnsigned()) {
return f.encoder().writeOp(Op::F32ConvertUI32);
}
if (inputType.isFloatish()) {
return true;
}
return f.failf(inputNode,
"%s is not a subtype of signed, unsigned, double? or floatish",
inputType.toChars());
}
template <typename Unit>
static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
Type ret, Type* type);
template <typename Unit>
static bool CheckCoercionArg(FunctionValidator<Unit>& f, ParseNode* arg,
Type expected, Type* type) {
MOZ_ASSERT(expected.isCanonicalValType());
if (arg->isKind(ParseNodeKind::CallExpr)) {
return CheckCoercedCall(f, arg, expected, type);
}
Type argType;
if (!CheckExpr(f, arg, &argType)) {
return false;
}
if (expected.isFloat()) {
if (!CheckFloatCoercionArg(f, arg, argType)) {
return false;
}
} else {
MOZ_CRASH("not call coercions");
}
*type = Type::ret(expected);
return true;
}
template <typename Unit>
static bool CheckMathFRound(FunctionValidator<Unit>& f, ParseNode* callNode,
Type* type) {
if (CallArgListLength(callNode) != 1) {
return f.fail(callNode, "Math.fround must be passed 1 argument");
}
ParseNode* argNode = CallArgList(callNode);
Type argType;
if (!CheckCoercionArg(f, argNode, Type::Float, &argType)) {
return false;
}
MOZ_ASSERT(argType == Type::Float);
*type = Type::Float;
return true;
}
template <typename Unit>
static bool CheckMathBuiltinCall(FunctionValidator<Unit>& f,
ParseNode* callNode,
AsmJSMathBuiltinFunction func, Type* type) {
unsigned arity = 0;
Op f32 = Op::Limit;
Op f64 = Op::Limit;
MozOp mozf64 = MozOp::Limit;
switch (func) {
case AsmJSMathBuiltin_imul:
return CheckMathIMul(f, callNode, type);
case AsmJSMathBuiltin_clz32:
return CheckMathClz32(f, callNode, type);
case AsmJSMathBuiltin_abs:
return CheckMathAbs(f, callNode, type);
case AsmJSMathBuiltin_sqrt:
return CheckMathSqrt(f, callNode, type);
case AsmJSMathBuiltin_fround:
return CheckMathFRound(f, callNode, type);
case AsmJSMathBuiltin_min:
return CheckMathMinMax(f, callNode, false, type);
case AsmJSMathBuiltin_max:
return CheckMathMinMax(f, callNode, true, type);
case AsmJSMathBuiltin_ceil:
arity = 1;
f64 = Op::F64Ceil;
f32 = Op::F32Ceil;
break;
case AsmJSMathBuiltin_floor:
arity = 1;
f64 = Op::F64Floor;
f32 = Op::F32Floor;
break;
case AsmJSMathBuiltin_sin:
arity = 1;
mozf64 = MozOp::F64Sin;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_cos:
arity = 1;
mozf64 = MozOp::F64Cos;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_tan:
arity = 1;
mozf64 = MozOp::F64Tan;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_asin:
arity = 1;
mozf64 = MozOp::F64Asin;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_acos:
arity = 1;
mozf64 = MozOp::F64Acos;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_atan:
arity = 1;
mozf64 = MozOp::F64Atan;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_exp:
arity = 1;
mozf64 = MozOp::F64Exp;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_log:
arity = 1;
mozf64 = MozOp::F64Log;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_pow:
arity = 2;
mozf64 = MozOp::F64Pow;
f32 = Op::Unreachable;
break;
case AsmJSMathBuiltin_atan2:
arity = 2;
mozf64 = MozOp::F64Atan2;
f32 = Op::Unreachable;
break;
default:
MOZ_CRASH("unexpected mathBuiltin function");
}
unsigned actualArity = CallArgListLength(callNode);
if (actualArity != arity) {
return f.failf(callNode, "call passed %u arguments, expected %u",
actualArity, arity);
}
if (!f.prepareCall(callNode)) {
return false;
}
Type firstType;
ParseNode* argNode = CallArgList(callNode);
if (!CheckExpr(f, argNode, &firstType)) {
return false;
}
if (!firstType.isMaybeFloat() && !firstType.isMaybeDouble()) {
return f.fail(
argNode,
"arguments to math call should be a subtype of double? or float?");
}
bool opIsDouble = firstType.isMaybeDouble();
if (!opIsDouble && f32 == Op::Unreachable) {
return f.fail(callNode, "math builtin cannot be used as float");
}
if (arity == 2) {
Type secondType;
argNode = NextNode(argNode);
if (!CheckExpr(f, argNode, &secondType)) {
return false;
}
if (firstType.isMaybeDouble() && !secondType.isMaybeDouble()) {
return f.fail(
argNode,
"both arguments to math builtin call should be the same type");
}
if (firstType.isMaybeFloat() && !secondType.isMaybeFloat()) {
return f.fail(
argNode,
"both arguments to math builtin call should be the same type");
}
}
if (opIsDouble) {
if (f64 != Op::Limit) {
if (!f.encoder().writeOp(f64)) {
return false;
}
} else {
if (!f.encoder().writeOp(mozf64)) {
return false;
}
}
} else {
if (!f.encoder().writeOp(f32)) {
return false;
}
}
*type = opIsDouble ? Type::Double : Type::Floatish;
return true;
}
template <typename Unit>
static bool CheckUncoercedCall(FunctionValidator<Unit>& f, ParseNode* expr,
Type* type) {
MOZ_ASSERT(expr->isKind(ParseNodeKind::CallExpr));
const ModuleValidatorShared::Global* global;
if (IsCallToGlobal(f.m(), expr, &global) && global->isMathFunction()) {
return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type);
}
return f.fail(
expr,
"all function calls must be calls to standard lib math functions,"
" ignored (via f(); or comma-expression), coerced to signed (via f()|0),"
" coerced to float (via fround(f())), or coerced to double (via +f())");
}
static bool CoerceResult(FunctionValidatorShared& f, ParseNode* expr,
Type expected, Type actual, Type* type) {
MOZ_ASSERT(expected.isCanonical());
switch (expected.which()) {
case Type::Void:
if (!actual.isVoid()) {
if (!f.encoder().writeOp(Op::Drop)) {
return false;
}
}
break;
case Type::Int:
if (!actual.isIntish()) {
return f.failf(expr, "%s is not a subtype of intish", actual.toChars());
}
break;
case Type::Float:
if (!CheckFloatCoercionArg(f, expr, actual)) {
return false;
}
break;
case Type::Double:
if (actual.isMaybeDouble()) {
} else if (actual.isMaybeFloat()) {
if (!f.encoder().writeOp(Op::F64PromoteF32)) {
return false;
}
} else if (actual.isSigned()) {
if (!f.encoder().writeOp(Op::F64ConvertSI32)) {
return false;
}
} else if (actual.isUnsigned()) {
if (!f.encoder().writeOp(Op::F64ConvertUI32)) {
return false;
}
} else {
return f.failf(
expr, "%s is not a subtype of double?, float?, signed or unsigned",
actual.toChars());
}
break;
default:
MOZ_CRASH("unexpected uncoerced result type");
}
*type = Type::ret(expected);
return true;
}
template <typename Unit>
static bool CheckCoercedMathBuiltinCall(FunctionValidator<Unit>& f,
ParseNode* callNode,
AsmJSMathBuiltinFunction func, Type ret,
Type* type) {
Type actual;
if (!CheckMathBuiltinCall(f, callNode, func, &actual)) {
return false;
}
return CoerceResult(f, callNode, ret, actual, type);
}
template <typename Unit>
static bool CheckCoercedCall(FunctionValidator<Unit>& f, ParseNode* call,
Type ret, Type* type) {
MOZ_ASSERT(ret.isCanonical());
if (!CheckRecursionLimitDontReport(f.cx())) {
return f.m().failOverRecursed();
}
if (IsNumericLiteral(f.m(), call)) {
NumLit lit = ExtractNumericLiteral(f.m(), call);
if (!f.writeConstExpr(lit)) {
return false;
}
return CoerceResult(f, call, ret, Type::lit(lit), type);
}
ParseNode* callee = CallCallee(call);
if (callee->isKind(ParseNodeKind::ElemExpr)) {
return CheckFuncPtrCall(f, call, ret, type);
}
if (!callee->isKind(ParseNodeKind::Name)) {
return f.fail(callee, "unexpected callee expression type");
}
PropertyName* calleeName = callee->as<NameNode>().name();
if (const ModuleValidatorShared::Global* global =
f.lookupGlobal(calleeName)) {
switch (global->which()) {
case ModuleValidatorShared::Global::FFI:
return CheckFFICall(f, call, global->ffiIndex(), ret, type);
case ModuleValidatorShared::Global::MathBuiltinFunction:
return CheckCoercedMathBuiltinCall(
f, call, global->mathBuiltinFunction(), ret, type);
case ModuleValidatorShared::Global::ConstantLiteral:
case ModuleValidatorShared::Global::ConstantImport:
case ModuleValidatorShared::Global::Variable:
case ModuleValidatorShared::Global::Table:
case ModuleValidatorShared::Global::ArrayView:
case ModuleValidatorShared::Global::ArrayViewCtor:
return f.failName(callee, "'%s' is not callable function", calleeName);
case ModuleValidatorShared::Global::Function:
break;
}
}
return CheckInternalCall(f, call, calleeName, ret, type);
}
template <typename Unit>
static bool CheckPos(FunctionValidator<Unit>& f, ParseNode* pos, Type* type) {
MOZ_ASSERT(pos->isKind(ParseNodeKind::PosExpr));
ParseNode* operand = UnaryKid(pos);
if (operand->isKind(ParseNodeKind::CallExpr)) {
return CheckCoercedCall(f, operand, Type::Double, type);
}
Type actual;
if (!CheckExpr(f, operand, &actual)) {
return false;
}
return CoerceResult(f, operand, Type::Double, actual, type);
}
template <typename Unit>
static bool CheckNot(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
MOZ_ASSERT(expr->isKind(ParseNodeKind::NotExpr));
ParseNode* operand = UnaryKid(expr);
Type operandType;
if (!CheckExpr(f, operand, &operandType)) {
return false;
}
if (!operandType.isInt()) {
return f.failf(operand, "%s is not a subtype of int",
operandType.toChars());
}
*type = Type::Int;
return f.encoder().writeOp(Op::I32Eqz);
}
template <typename Unit>
static bool CheckNeg(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
MOZ_ASSERT(expr->isKind(ParseNodeKind::NegExpr));
ParseNode* operand = UnaryKid(expr);
Type operandType;
if (!CheckExpr(f, operand, &operandType)) {
return false;
}
if (operandType.isInt()) {
*type = Type::Intish;
return f.encoder().writeOp(MozOp::I32Neg);
}
if (operandType.isMaybeDouble()) {
*type = Type::Double;
return f.encoder().writeOp(Op::F64Neg);
}
if (operandType.isMaybeFloat()) {
*type = Type::Floatish;
return f.encoder().writeOp(Op::F32Neg);
}
return f.failf(operand, "%s is not a subtype of int, float? or double?",
operandType.toChars());
}
template <typename Unit>
static bool CheckCoerceToInt(FunctionValidator<Unit>& f, ParseNode* expr,
Type* type) {
MOZ_ASSERT(expr->isKind(ParseNodeKind::BitNotExpr));
ParseNode* operand = UnaryKid(expr);
Type operandType;
if (!CheckExpr(f, operand, &operandType)) {
return false;
}
if (operandType.isMaybeDouble() || operandType.isMaybeFloat()) {
*type = Type::Signed;
Op opcode =
operandType.isMaybeDouble() ? Op::I32TruncSF64 : Op::I32TruncSF32;
return f.encoder().writeOp(opcode);
}
if (!operandType.isIntish()) {
return f.failf(operand, "%s is not a subtype of double?, float? or intish",
operandType.toChars());
}
*type = Type::Signed;
return true;
}
template <typename Unit>
static bool CheckBitNot(FunctionValidator<Unit>& f, ParseNode* neg,
Type* type) {
MOZ_ASSERT(neg->isKind(ParseNodeKind::BitNotExpr));
ParseNode* operand = UnaryKid(neg);
if (operand->isKind(ParseNodeKind::BitNotExpr)) {
return CheckCoerceToInt(f, operand, type);
}
Type operandType;
if (!CheckExpr(f, operand, &operandType)) {
return false;
}
if (!operandType.isIntish()) {
return f.failf(operand, "%s is not a subtype of intish",
operandType.toChars());
}
if (!f.encoder().writeOp(MozOp::I32BitNot)) {
return false;
}
*type = Type::Signed;
return true;
}
template <typename Unit>
static bool CheckAsExprStatement(FunctionValidator<Unit>& f,
ParseNode* exprStmt);
template <typename Unit>
static bool CheckComma(FunctionValidator<Unit>& f, ParseNode* comma,
Type* type) {
MOZ_ASSERT(comma->isKind(ParseNodeKind::CommaExpr));
ParseNode* operands = ListHead(comma);
if (!f.encoder().writeOp(Op::Block)) {
return false;
}
size_t typeAt;
if (!f.encoder().writePatchableFixedU7(&typeAt)) {
return false;
}
ParseNode* pn = operands;
for (; NextNode(pn); pn = NextNode(pn)) {
if (!CheckAsExprStatement(f, pn)) {
return false;
}
}
if (!CheckExpr(f, pn, type)) {
return false;
}
f.encoder().patchFixedU7(typeAt,
uint8_t(type->toWasmBlockSignatureType().code()));
return f.encoder().writeOp(Op::End);
}
template <typename Unit>
static bool CheckConditional(FunctionValidator<Unit>& f, ParseNode* ternary,
Type* type) {
MOZ_ASSERT(ternary->isKind(ParseNodeKind::ConditionalExpr));
ParseNode* cond = TernaryKid1(ternary);
ParseNode* thenExpr = TernaryKid2(ternary);
ParseNode* elseExpr = TernaryKid3(ternary);
Type condType;
if (!CheckExpr(f, cond, &condType)) {
return false;
}
if (!condType.isInt()) {
return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
size_t typeAt;
if (!f.pushIf(&typeAt)) {
return false;
}
Type thenType;
if (!CheckExpr(f, thenExpr, &thenType)) {
return false;
}
if (!f.switchToElse()) {
return false;
}
Type elseType;
if (!CheckExpr(f, elseExpr, &elseType)) {
return false;
}
if (thenType.isInt() && elseType.isInt()) {
*type = Type::Int;
} else if (thenType.isDouble() && elseType.isDouble()) {
*type = Type::Double;
} else if (thenType.isFloat() && elseType.isFloat()) {
*type = Type::Float;
} else {
return f.failf(
ternary,
"then/else branches of conditional must both produce int, float, "
"double, current types are %s and %s",
thenType.toChars(), elseType.toChars());
}
if (!f.popIf(typeAt, type->toWasmBlockSignatureType())) {
return false;
}
return true;
}
template <typename Unit>
static bool IsValidIntMultiplyConstant(ModuleValidator<Unit>& m,
ParseNode* expr) {
if (!IsNumericLiteral(m, expr)) {
return false;
}
NumLit lit = ExtractNumericLiteral(m, expr);
switch (lit.which()) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
if (Abs(lit.toInt32()) < (uint32_t(1) << 20)) {
return true;
}
return false;
case NumLit::BigUnsigned:
case NumLit::Double:
case NumLit::Float:
case NumLit::OutOfRangeInt:
return false;
}
MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal");
}
template <typename Unit>
static bool CheckMultiply(FunctionValidator<Unit>& f, ParseNode* star,
Type* type) {
MOZ_ASSERT(star->isKind(ParseNodeKind::MulExpr));
ParseNode* lhs = MultiplyLeft(star);
ParseNode* rhs = MultiplyRight(star);
Type lhsType;
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (lhsType.isInt() && rhsType.isInt()) {
if (!IsValidIntMultiplyConstant(f.m(), lhs) &&
!IsValidIntMultiplyConstant(f.m(), rhs)) {
return f.fail(
star,
"one arg to int multiply must be a small (-2^20, 2^20) int literal");
}
*type = Type::Intish;
return f.encoder().writeOp(Op::I32Mul);
}
if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
*type = Type::Double;
return f.encoder().writeOp(Op::F64Mul);
}
if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
*type = Type::Floatish;
return f.encoder().writeOp(Op::F32Mul);
}
return f.fail(
star, "multiply operands must be both int, both double? or both float?");
}
template <typename Unit>
static bool CheckAddOrSub(FunctionValidator<Unit>& f, ParseNode* expr,
Type* type, unsigned* numAddOrSubOut = nullptr) {
if (!CheckRecursionLimitDontReport(f.cx())) {
return f.m().failOverRecursed();
}
MOZ_ASSERT(expr->isKind(ParseNodeKind::AddExpr) ||
expr->isKind(ParseNodeKind::SubExpr));
ParseNode* lhs = AddSubLeft(expr);
ParseNode* rhs = AddSubRight(expr);
Type lhsType, rhsType;
unsigned lhsNumAddOrSub, rhsNumAddOrSub;
if (lhs->isKind(ParseNodeKind::AddExpr) ||
lhs->isKind(ParseNodeKind::SubExpr)) {
if (!CheckAddOrSub(f, lhs, &lhsType, &lhsNumAddOrSub)) {
return false;
}
if (lhsType == Type::Intish) {
lhsType = Type::Int;
}
} else {
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
lhsNumAddOrSub = 0;
}
if (rhs->isKind(ParseNodeKind::AddExpr) ||
rhs->isKind(ParseNodeKind::SubExpr)) {
if (!CheckAddOrSub(f, rhs, &rhsType, &rhsNumAddOrSub)) {
return false;
}
if (rhsType == Type::Intish) {
rhsType = Type::Int;
}
} else {
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
rhsNumAddOrSub = 0;
}
unsigned numAddOrSub = lhsNumAddOrSub + rhsNumAddOrSub + 1;
if (numAddOrSub > (1 << 20)) {
return f.fail(expr, "too many + or - without intervening coercion");
}
if (lhsType.isInt() && rhsType.isInt()) {
if (!f.encoder().writeOp(
expr->isKind(ParseNodeKind::AddExpr) ? Op::I32Add : Op::I32Sub)) {
return false;
}
*type = Type::Intish;
} else if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
if (!f.encoder().writeOp(
expr->isKind(ParseNodeKind::AddExpr) ? Op::F64Add : Op::F64Sub)) {
return false;
}
*type = Type::Double;
} else if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
if (!f.encoder().writeOp(
expr->isKind(ParseNodeKind::AddExpr) ? Op::F32Add : Op::F32Sub)) {
return false;
}
*type = Type::Floatish;
} else {
return f.failf(
expr,
"operands to + or - must both be int, float? or double?, got %s and %s",
lhsType.toChars(), rhsType.toChars());
}
if (numAddOrSubOut) {
*numAddOrSubOut = numAddOrSub;
}
return true;
}
template <typename Unit>
static bool CheckDivOrMod(FunctionValidator<Unit>& f, ParseNode* expr,
Type* type) {
MOZ_ASSERT(expr->isKind(ParseNodeKind::DivExpr) ||
expr->isKind(ParseNodeKind::ModExpr));
ParseNode* lhs = DivOrModLeft(expr);
ParseNode* rhs = DivOrModRight(expr);
Type lhsType, rhsType;
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) {
*type = Type::Double;
if (expr->isKind(ParseNodeKind::DivExpr)) {
return f.encoder().writeOp(Op::F64Div);
}
return f.encoder().writeOp(MozOp::F64Mod);
}
if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) {
*type = Type::Floatish;
if (expr->isKind(ParseNodeKind::DivExpr)) {
return f.encoder().writeOp(Op::F32Div);
} else {
return f.fail(expr, "modulo cannot receive float arguments");
}
}
if (lhsType.isSigned() && rhsType.isSigned()) {
*type = Type::Intish;
return f.encoder().writeOp(
expr->isKind(ParseNodeKind::DivExpr) ? Op::I32DivS : Op::I32RemS);
}
if (lhsType.isUnsigned() && rhsType.isUnsigned()) {
*type = Type::Intish;
return f.encoder().writeOp(
expr->isKind(ParseNodeKind::DivExpr) ? Op::I32DivU : Op::I32RemU);
}
return f.failf(
expr,
"arguments to / or %% must both be double?, float?, signed, or unsigned; "
"%s and %s are given",
lhsType.toChars(), rhsType.toChars());
}
template <typename Unit>
static bool CheckComparison(FunctionValidator<Unit>& f, ParseNode* comp,
Type* type) {
MOZ_ASSERT(comp->isKind(ParseNodeKind::LtExpr) ||
comp->isKind(ParseNodeKind::LeExpr) ||
comp->isKind(ParseNodeKind::GtExpr) ||
comp->isKind(ParseNodeKind::GeExpr) ||
comp->isKind(ParseNodeKind::EqExpr) ||
comp->isKind(ParseNodeKind::NeExpr));
ParseNode* lhs = ComparisonLeft(comp);
ParseNode* rhs = ComparisonRight(comp);
Type lhsType, rhsType;
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (!(lhsType.isSigned() && rhsType.isSigned()) &&
!(lhsType.isUnsigned() && rhsType.isUnsigned()) &&
!(lhsType.isDouble() && rhsType.isDouble()) &&
!(lhsType.isFloat() && rhsType.isFloat())) {
return f.failf(comp,
"arguments to a comparison must both be signed, unsigned, "
"floats or doubles; "
"%s and %s are given",
lhsType.toChars(), rhsType.toChars());
}
Op stmt;
if (lhsType.isSigned() && rhsType.isSigned()) {
switch (comp->getKind()) {
case ParseNodeKind::EqExpr:
stmt = Op::I32Eq;
break;
case ParseNodeKind::NeExpr:
stmt = Op::I32Ne;
break;
case ParseNodeKind::LtExpr:
stmt = Op::I32LtS;
break;
case ParseNodeKind::LeExpr:
stmt = Op::I32LeS;
break;
case ParseNodeKind::GtExpr:
stmt = Op::I32GtS;
break;
case ParseNodeKind::GeExpr:
stmt = Op::I32GeS;
break;
default:
MOZ_CRASH("unexpected comparison op");
}
} else if (lhsType.isUnsigned() && rhsType.isUnsigned()) {
switch (comp->getKind()) {
case ParseNodeKind::EqExpr:
stmt = Op::I32Eq;
break;
case ParseNodeKind::NeExpr:
stmt = Op::I32Ne;
break;
case ParseNodeKind::LtExpr:
stmt = Op::I32LtU;
break;
case ParseNodeKind::LeExpr:
stmt = Op::I32LeU;
break;
case ParseNodeKind::GtExpr:
stmt = Op::I32GtU;
break;
case ParseNodeKind::GeExpr:
stmt = Op::I32GeU;
break;
default:
MOZ_CRASH("unexpected comparison op");
}
} else if (lhsType.isDouble()) {
switch (comp->getKind()) {
case ParseNodeKind::EqExpr:
stmt = Op::F64Eq;
break;
case ParseNodeKind::NeExpr:
stmt = Op::F64Ne;
break;
case ParseNodeKind::LtExpr:
stmt = Op::F64Lt;
break;
case ParseNodeKind::LeExpr:
stmt = Op::F64Le;
break;
case ParseNodeKind::GtExpr:
stmt = Op::F64Gt;
break;
case ParseNodeKind::GeExpr:
stmt = Op::F64Ge;
break;
default:
MOZ_CRASH("unexpected comparison op");
}
} else if (lhsType.isFloat()) {
switch (comp->getKind()) {
case ParseNodeKind::EqExpr:
stmt = Op::F32Eq;
break;
case ParseNodeKind::NeExpr:
stmt = Op::F32Ne;
break;
case ParseNodeKind::LtExpr:
stmt = Op::F32Lt;
break;
case ParseNodeKind::LeExpr:
stmt = Op::F32Le;
break;
case ParseNodeKind::GtExpr:
stmt = Op::F32Gt;
break;
case ParseNodeKind::GeExpr:
stmt = Op::F32Ge;
break;
default:
MOZ_CRASH("unexpected comparison op");
}
} else {
MOZ_CRASH("unexpected type");
}
*type = Type::Int;
return f.encoder().writeOp(stmt);
}
template <typename Unit>
static bool CheckBitwise(FunctionValidator<Unit>& f, ParseNode* bitwise,
Type* type) {
ParseNode* lhs = BitwiseLeft(bitwise);
ParseNode* rhs = BitwiseRight(bitwise);
int32_t identityElement;
bool onlyOnRight;
switch (bitwise->getKind()) {
case ParseNodeKind::BitOrExpr:
identityElement = 0;
onlyOnRight = false;
*type = Type::Signed;
break;
case ParseNodeKind::BitAndExpr:
identityElement = -1;
onlyOnRight = false;
*type = Type::Signed;
break;
case ParseNodeKind::BitXorExpr:
identityElement = 0;
onlyOnRight = false;
*type = Type::Signed;
break;
case ParseNodeKind::LshExpr:
identityElement = 0;
onlyOnRight = true;
*type = Type::Signed;
break;
case ParseNodeKind::RshExpr:
identityElement = 0;
onlyOnRight = true;
*type = Type::Signed;
break;
case ParseNodeKind::UrshExpr:
identityElement = 0;
onlyOnRight = true;
*type = Type::Unsigned;
break;
default:
MOZ_CRASH("not a bitwise op");
}
uint32_t i;
if (!onlyOnRight && IsLiteralInt(f.m(), lhs, &i) &&
i == uint32_t(identityElement)) {
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (!rhsType.isIntish()) {
return f.failf(bitwise, "%s is not a subtype of intish",
rhsType.toChars());
}
return true;
}
if (IsLiteralInt(f.m(), rhs, &i) && i == uint32_t(identityElement)) {
if (bitwise->isKind(ParseNodeKind::BitOrExpr) &&
lhs->isKind(ParseNodeKind::CallExpr)) {
return CheckCoercedCall(f, lhs, Type::Int, type);
}
Type lhsType;
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
if (!lhsType.isIntish()) {
return f.failf(bitwise, "%s is not a subtype of intish",
lhsType.toChars());
}
return true;
}
Type lhsType;
if (!CheckExpr(f, lhs, &lhsType)) {
return false;
}
Type rhsType;
if (!CheckExpr(f, rhs, &rhsType)) {
return false;
}
if (!lhsType.isIntish()) {
return f.failf(lhs, "%s is not a subtype of intish", lhsType.toChars());
}
if (!rhsType.isIntish()) {
return f.failf(rhs, "%s is not a subtype of intish", rhsType.toChars());
}
switch (bitwise->getKind()) {
case ParseNodeKind::BitOrExpr:
if (!f.encoder().writeOp(Op::I32Or)) return false;
break;
case ParseNodeKind::BitAndExpr:
if (!f.encoder().writeOp(Op::I32And)) return false;
break;
case ParseNodeKind::BitXorExpr:
if (!f.encoder().writeOp(Op::I32Xor)) return false;
break;
case ParseNodeKind::LshExpr:
if (!f.encoder().writeOp(Op::I32Shl)) return false;
break;
case ParseNodeKind::RshExpr:
if (!f.encoder().writeOp(Op::I32ShrS)) return false;
break;
case ParseNodeKind::UrshExpr:
if (!f.encoder().writeOp(Op::I32ShrU)) return false;
break;
default:
MOZ_CRASH("not a bitwise op");
}
return true;
}
template <typename Unit>
static bool CheckExpr(FunctionValidator<Unit>& f, ParseNode* expr, Type* type) {
if (!CheckRecursionLimitDontReport(f.cx())) {
return f.m().failOverRecursed();
}
if (IsNumericLiteral(f.m(), expr)) {
return CheckNumericLiteral(f, expr, type);
}
switch (expr->getKind()) {
case ParseNodeKind::Name:
return CheckVarRef(f, expr, type);
case ParseNodeKind::ElemExpr:
return CheckLoadArray(f, expr, type);
case ParseNodeKind::AssignExpr:
return CheckAssign(f, expr, type);
case ParseNodeKind::PosExpr:
return CheckPos(f, expr, type);
case ParseNodeKind::NotExpr:
return CheckNot(f, expr, type);
case ParseNodeKind::NegExpr:
return CheckNeg(f, expr, type);
case ParseNodeKind::BitNotExpr:
return CheckBitNot(f, expr, type);
case ParseNodeKind::CommaExpr:
return CheckComma(f, expr, type);
case ParseNodeKind::ConditionalExpr:
return CheckConditional(f, expr, type);
case ParseNodeKind::MulExpr:
return CheckMultiply(f, expr, type);
case ParseNodeKind::CallExpr:
return CheckUncoercedCall(f, expr, type);
case ParseNodeKind::AddExpr:
case ParseNodeKind::SubExpr:
return CheckAddOrSub(f, expr, type);
case ParseNodeKind::DivExpr:
case ParseNodeKind::ModExpr:
return CheckDivOrMod(f, expr, type);
case ParseNodeKind::LtExpr:
case ParseNodeKind::LeExpr:
case ParseNodeKind::GtExpr:
case ParseNodeKind::GeExpr:
case ParseNodeKind::EqExpr:
case ParseNodeKind::NeExpr:
return CheckComparison(f, expr, type);
case ParseNodeKind::BitOrExpr:
case ParseNodeKind::BitAndExpr:
case ParseNodeKind::BitXorExpr:
case ParseNodeKind::LshExpr:
case ParseNodeKind::RshExpr:
case ParseNodeKind::UrshExpr:
return CheckBitwise(f, expr, type);
default:;
}
return f.fail(expr, "unsupported expression");
}
template <typename Unit>
static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt);
template <typename Unit>
static bool CheckAsExprStatement(FunctionValidator<Unit>& f, ParseNode* expr) {
if (expr->isKind(ParseNodeKind::CallExpr)) {
Type ignored;
return CheckCoercedCall(f, expr, Type::Void, &ignored);
}
Type resultType;
if (!CheckExpr(f, expr, &resultType)) {
return false;
}
if (!resultType.isVoid()) {
if (!f.encoder().writeOp(Op::Drop)) {
return false;
}
}
return true;
}
template <typename Unit>
static bool CheckExprStatement(FunctionValidator<Unit>& f,
ParseNode* exprStmt) {
MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt));
return CheckAsExprStatement(f, UnaryKid(exprStmt));
}
template <typename Unit>
static bool CheckLoopConditionOnEntry(FunctionValidator<Unit>& f,
ParseNode* cond) {
uint32_t maybeLit;
if (IsLiteralInt(f.m(), cond, &maybeLit) && maybeLit) {
return true;
}
Type condType;
if (!CheckExpr(f, cond, &condType)) {
return false;
}
if (!condType.isInt()) {
return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
if (!f.encoder().writeOp(Op::I32Eqz)) {
return false;
}
if (!f.writeBreakIf()) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
const LabelVector* labels = nullptr) {
MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::WhileStmt));
ParseNode* cond = BinaryLeft(whileStmt);
ParseNode* body = BinaryRight(whileStmt);
if (labels && !f.addLabels(*labels, 0, 1)) {
return false;
}
if (!f.pushLoop()) {
return false;
}
if (!CheckLoopConditionOnEntry(f, cond)) {
return false;
}
if (!CheckStatement(f, body)) {
return false;
}
if (!f.writeContinue()) {
return false;
}
if (!f.popLoop()) {
return false;
}
if (labels) {
f.removeLabels(*labels);
}
return true;
}
template <typename Unit>
static bool CheckFor(FunctionValidator<Unit>& f, ParseNode* forStmt,
const LabelVector* labels = nullptr) {
MOZ_ASSERT(forStmt->isKind(ParseNodeKind::ForStmt));
ParseNode* forHead = BinaryLeft(forStmt);
ParseNode* body = BinaryRight(forStmt);
if (!forHead->isKind(ParseNodeKind::ForHead)) {
return f.fail(forHead, "unsupported for-loop statement");
}
ParseNode* maybeInit = TernaryKid1(forHead);
ParseNode* maybeCond = TernaryKid2(forHead);
ParseNode* maybeInc = TernaryKid3(forHead);
if (labels && !f.addLabels(*labels, 1, 3)) {
return false;
}
if (!f.pushUnbreakableBlock()) {
return false;
}
if (maybeInit && !CheckAsExprStatement(f, maybeInit)) {
return false;
}
{
if (!f.pushLoop()) {
return false;
}
if (maybeCond && !CheckLoopConditionOnEntry(f, maybeCond)) {
return false;
}
{
if (!f.pushContinuableBlock()) {
return false;
}
if (!CheckStatement(f, body)) {
return false;
}
if (!f.popContinuableBlock()) {
return false;
}
}
if (maybeInc && !CheckAsExprStatement(f, maybeInc)) {
return false;
}
if (!f.writeContinue()) {
return false;
}
if (!f.popLoop()) {
return false;
}
}
if (!f.popUnbreakableBlock()) {
return false;
}
if (labels) {
f.removeLabels(*labels);
}
return true;
}
template <typename Unit>
static bool CheckDoWhile(FunctionValidator<Unit>& f, ParseNode* whileStmt,
const LabelVector* labels = nullptr) {
MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::DoWhileStmt));
ParseNode* body = BinaryLeft(whileStmt);
ParseNode* cond = BinaryRight(whileStmt);
if (labels && !f.addLabels(*labels, 0, 2)) {
return false;
}
if (!f.pushLoop()) {
return false;
}
{
if (!f.pushContinuableBlock()) {
return false;
}
if (!CheckStatement(f, body)) {
return false;
}
if (!f.popContinuableBlock()) {
return false;
}
}
Type condType;
if (!CheckExpr(f, cond, &condType)) {
return false;
}
if (!condType.isInt()) {
return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
if (!f.writeContinueIf()) {
return false;
}
if (!f.popLoop()) {
return false;
}
if (labels) {
f.removeLabels(*labels);
}
return true;
}
template <typename Unit>
static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode*,
const LabelVector* = nullptr);
template <typename Unit>
static bool CheckLabel(FunctionValidator<Unit>& f, ParseNode* labeledStmt) {
MOZ_ASSERT(labeledStmt->isKind(ParseNodeKind::LabelStmt));
LabelVector labels;
ParseNode* innermost = labeledStmt;
do {
if (!labels.append(LabeledStatementLabel(innermost))) {
return false;
}
innermost = LabeledStatementStatement(innermost);
} while (innermost->getKind() == ParseNodeKind::LabelStmt);
switch (innermost->getKind()) {
case ParseNodeKind::ForStmt:
return CheckFor(f, innermost, &labels);
case ParseNodeKind::DoWhileStmt:
return CheckDoWhile(f, innermost, &labels);
case ParseNodeKind::WhileStmt:
return CheckWhile(f, innermost, &labels);
case ParseNodeKind::StatementList:
return CheckStatementList(f, innermost, &labels);
default:
break;
}
if (!f.pushUnbreakableBlock(&labels)) {
return false;
}
if (!CheckStatement(f, innermost)) {
return false;
}
if (!f.popUnbreakableBlock(&labels)) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckIf(FunctionValidator<Unit>& f, ParseNode* ifStmt) {
uint32_t numIfEnd = 1;
recurse:
MOZ_ASSERT(ifStmt->isKind(ParseNodeKind::IfStmt));
ParseNode* cond = TernaryKid1(ifStmt);
ParseNode* thenStmt = TernaryKid2(ifStmt);
ParseNode* elseStmt = TernaryKid3(ifStmt);
Type condType;
if (!CheckExpr(f, cond, &condType)) {
return false;
}
if (!condType.isInt()) {
return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
size_t typeAt;
if (!f.pushIf(&typeAt)) {
return false;
}
f.setIfType(typeAt, ExprType::Void);
if (!CheckStatement(f, thenStmt)) {
return false;
}
if (elseStmt) {
if (!f.switchToElse()) {
return false;
}
if (elseStmt->isKind(ParseNodeKind::IfStmt)) {
ifStmt = elseStmt;
if (numIfEnd++ == UINT32_MAX) {
return false;
}
goto recurse;
}
if (!CheckStatement(f, elseStmt)) {
return false;
}
}
for (uint32_t i = 0; i != numIfEnd; ++i) {
if (!f.popIf()) {
return false;
}
}
return true;
}
static bool CheckCaseExpr(FunctionValidatorShared& f, ParseNode* caseExpr,
int32_t* value) {
if (!IsNumericLiteral(f.m(), caseExpr)) {
return f.fail(caseExpr,
"switch case expression must be an integer literal");
}
NumLit lit = ExtractNumericLiteral(f.m(), caseExpr);
switch (lit.which()) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
*value = lit.toInt32();
break;
case NumLit::OutOfRangeInt:
case NumLit::BigUnsigned:
return f.fail(caseExpr, "switch case expression out of integer range");
case NumLit::Double:
case NumLit::Float:
return f.fail(caseExpr,
"switch case expression must be an integer literal");
}
return true;
}
static bool CheckDefaultAtEnd(FunctionValidatorShared& f, ParseNode* stmt) {
for (; stmt; stmt = NextNode(stmt)) {
if (IsDefaultCase(stmt) && NextNode(stmt) != nullptr) {
return f.fail(stmt, "default label must be at the end");
}
}
return true;
}
static bool CheckSwitchRange(FunctionValidatorShared& f, ParseNode* stmt,
int32_t* low, int32_t* high,
uint32_t* tableLength) {
if (IsDefaultCase(stmt)) {
*low = 0;
*high = -1;
*tableLength = 0;
return true;
}
int32_t i = 0;
if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) {
return false;
}
*low = *high = i;
ParseNode* initialStmt = stmt;
for (stmt = NextNode(stmt); stmt && !IsDefaultCase(stmt);
stmt = NextNode(stmt)) {
int32_t i = 0;
if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) {
return false;
}
*low = Min(*low, i);
*high = Max(*high, i);
}
int64_t i64 = (int64_t(*high) - int64_t(*low)) + 1;
if (i64 > MaxBrTableElems) {
return f.fail(
initialStmt,
"all switch statements generate tables; this table would be too big");
}
*tableLength = uint32_t(i64);
return true;
}
template <typename Unit>
static bool CheckSwitchExpr(FunctionValidator<Unit>& f, ParseNode* switchExpr) {
Type exprType;
if (!CheckExpr(f, switchExpr, &exprType)) {
return false;
}
if (!exprType.isSigned()) {
return f.failf(switchExpr, "%s is not a subtype of signed",
exprType.toChars());
}
return true;
}
template <typename Unit>
static bool CheckSwitch(FunctionValidator<Unit>& f, ParseNode* switchStmt) {
MOZ_ASSERT(switchStmt->isKind(ParseNodeKind::SwitchStmt));
ParseNode* switchExpr = BinaryLeft(switchStmt);
ParseNode* switchBody = BinaryRight(switchStmt);
if (switchBody->is<LexicalScopeNode>()) {
LexicalScopeNode* scope = &switchBody->as<LexicalScopeNode>();
if (!scope->isEmptyScope()) {
return f.fail(scope, "switch body may not contain lexical declarations");
}
switchBody = scope->scopeBody();
}
ParseNode* stmt = ListHead(switchBody);
if (!stmt) {
if (!CheckSwitchExpr(f, switchExpr)) {
return false;
}
if (!f.encoder().writeOp(Op::Drop)) {
return false;
}
return true;
}
if (!CheckDefaultAtEnd(f, stmt)) {
return false;
}
int32_t low = 0, high = 0;
uint32_t tableLength = 0;
if (!CheckSwitchRange(f, stmt, &low, &high, &tableLength)) {
return false;
}
static const uint32_t CASE_NOT_DEFINED = UINT32_MAX;
Uint32Vector caseDepths;
if (!caseDepths.appendN(CASE_NOT_DEFINED, tableLength)) {
return false;
}
uint32_t numCases = 0;
for (ParseNode* s = stmt; s && !IsDefaultCase(s); s = NextNode(s)) {
int32_t caseValue = ExtractNumericLiteral(f.m(), CaseExpr(s)).toInt32();
MOZ_ASSERT(caseValue >= low);
unsigned i = caseValue - low;
if (caseDepths[i] != CASE_NOT_DEFINED) {
return f.fail(s, "no duplicate case labels");
}
MOZ_ASSERT(numCases != CASE_NOT_DEFINED);
caseDepths[i] = numCases++;
}
if (!f.pushBreakableBlock()) {
return false;
}
for (uint32_t i = 0; i < numCases; i++) {
if (!f.pushUnbreakableBlock()) {
return false;
}
}
if (!f.pushUnbreakableBlock()) {
return false;
}
uint32_t defaultDepth = numCases;
if (low) {
if (!CheckSwitchExpr(f, switchExpr)) {
return false;
}
if (!f.writeInt32Lit(low)) {
return false;
}
if (!f.encoder().writeOp(Op::I32Sub)) {
return false;
}
} else {
if (!CheckSwitchExpr(f, switchExpr)) {
return false;
}
}
if (!f.encoder().writeOp(Op::BrTable)) {
return false;
}
if (!f.encoder().writeVarU32(tableLength)) {
return false;
}
for (size_t i = 0; i < tableLength; i++) {
uint32_t target =
caseDepths[i] == CASE_NOT_DEFINED ? defaultDepth : caseDepths[i];
if (!f.encoder().writeVarU32(target)) {
return false;
}
}
if (!f.encoder().writeVarU32(defaultDepth)) {
return false;
}
if (!f.popUnbreakableBlock()) {
return false;
}
for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) {
if (!CheckStatement(f, CaseBody(stmt))) {
return false;
}
if (!f.popUnbreakableBlock()) {
return false;
}
}
if (stmt && IsDefaultCase(stmt)) {
if (!CheckStatement(f, CaseBody(stmt))) {
return false;
}
}
if (!f.popBreakableBlock()) {
return false;
}
return true;
}
static bool CheckReturnType(FunctionValidatorShared& f, ParseNode* usepn,
Type ret) {
if (!f.hasAlreadyReturned()) {
f.setReturnedType(ret.canonicalToExprType());
return true;
}
if (f.returnedType() != ret.canonicalToExprType()) {
return f.failf(usepn, "%s incompatible with previous return of type %s",
Type::ret(ret).toChars(), ToCString(f.returnedType()));
}
return true;
}
template <typename Unit>
static bool CheckReturn(FunctionValidator<Unit>& f, ParseNode* returnStmt) {
ParseNode* expr = ReturnExpr(returnStmt);
if (!expr) {
if (!CheckReturnType(f, returnStmt, Type::Void)) {
return false;
}
} else {
Type type;
if (!CheckExpr(f, expr, &type)) {
return false;
}
if (!type.isReturnType()) {
return f.failf(expr, "%s is not a valid return type", type.toChars());
}
if (!CheckReturnType(f, expr, Type::canonicalize(type))) {
return false;
}
}
if (!f.encoder().writeOp(Op::Return)) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckStatementList(FunctionValidator<Unit>& f, ParseNode* stmtList,
const LabelVector* labels ) {
MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
if (!f.pushUnbreakableBlock(labels)) {
return false;
}
for (ParseNode* stmt = ListHead(stmtList); stmt; stmt = NextNode(stmt)) {
if (!CheckStatement(f, stmt)) {
return false;
}
}
if (!f.popUnbreakableBlock(labels)) {
return false;
}
return true;
}
template <typename Unit>
static bool CheckLexicalScope(FunctionValidator<Unit>& f, ParseNode* node) {
LexicalScopeNode* lexicalScope = &node->as<LexicalScopeNode>();
if (!lexicalScope->isEmptyScope()) {
return f.fail(lexicalScope, "cannot have 'let' or 'const' declarations");
}
return CheckStatement(f, lexicalScope->scopeBody());
}
static bool CheckBreakOrContinue(FunctionValidatorShared& f, bool isBreak,
ParseNode* stmt) {
if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt)) {
return f.writeLabeledBreakOrContinue(maybeLabel, isBreak);
}
return f.writeUnlabeledBreakOrContinue(isBreak);
}
template <typename Unit>
static bool CheckStatement(FunctionValidator<Unit>& f, ParseNode* stmt) {
if (!CheckRecursionLimitDontReport(f.cx())) {
return f.m().failOverRecursed();
}
switch (stmt->getKind()) {
case ParseNodeKind::EmptyStmt:
return true;
case ParseNodeKind::ExpressionStmt:
return CheckExprStatement(f, stmt);
case ParseNodeKind::WhileStmt:
return CheckWhile(f, stmt);
case ParseNodeKind::ForStmt:
return CheckFor(f, stmt);
case ParseNodeKind::DoWhileStmt:
return CheckDoWhile(f, stmt);
case ParseNodeKind::LabelStmt:
return CheckLabel(f, stmt);
case ParseNodeKind::IfStmt:
return CheckIf(f, stmt);
case ParseNodeKind::SwitchStmt:
return CheckSwitch(f, stmt);
case ParseNodeKind::ReturnStmt:
return CheckReturn(f, stmt);
case ParseNodeKind::StatementList:
return CheckStatementList(f, stmt);
case ParseNodeKind::BreakStmt:
return CheckBreakOrContinue(f, true, stmt);
case ParseNodeKind::ContinueStmt:
return CheckBreakOrContinue(f, false, stmt);
case ParseNodeKind::LexicalScope:
return CheckLexicalScope(f, stmt);
default:;
}
return f.fail(stmt, "unexpected statement kind");
}
template <typename Unit>
static bool ParseFunction(ModuleValidator<Unit>& m, FunctionNode** funNodeOut,
unsigned* line) {
auto& tokenStream = m.tokenStream();
tokenStream.consumeKnownToken(TokenKind::Function,
TokenStreamShared::Operand);
auto& anyChars = tokenStream.anyCharsAccess();
uint32_t toStringStart = anyChars.currentToken().pos.begin;
*line = anyChars.lineNumber(anyChars.lineToken(toStringStart));
TokenKind tk;
if (!tokenStream.getToken(&tk, TokenStreamShared::Operand)) {
return false;
}
if (tk == TokenKind::Mul) {
return m.failCurrentOffset("unexpected generator function");
}
if (!TokenKindIsPossibleIdentifier(tk)) {
return false; }
RootedPropertyName name(m.cx(), m.parser().bindingIdentifier(YieldIsName));
if (!name) {
return false;
}
FunctionNode* funNode = m.parser().handler_.newFunction(
FunctionSyntaxKind::Statement, m.parser().pos());
if (!funNode) {
return false;
}
RootedFunction& fun = m.dummyFunction();
fun->setAtom(name);
fun->setArgCount(0);
ParseContext* outerpc = m.parser().pc_;
Directives directives(outerpc);
FunctionBox* funbox = m.parser().newFunctionBox(
funNode, fun, toStringStart, directives, GeneratorKind::NotGenerator,
FunctionAsyncKind::SyncFunction);
if (!funbox) {
return false;
}
funbox->initWithEnclosingParseContext(outerpc, FunctionSyntaxKind::Statement);
Directives newDirectives = directives;
SourceParseContext funpc(&m.parser(), funbox, &newDirectives);
if (!funpc.init()) {
return false;
}
if (!m.parser().functionFormalParametersAndBody(
InAllowed, YieldIsName, &funNode, FunctionSyntaxKind::Statement)) {
if (anyChars.hadError() || directives == newDirectives) {
return false;
}
return m.fail(funNode, "encountered new directive in function");
}
MOZ_ASSERT(!anyChars.hadError());
MOZ_ASSERT(directives == newDirectives);
*funNodeOut = funNode;
return true;
}
template <typename Unit>
static bool CheckFunction(ModuleValidator<Unit>& m) {
frontend::ParserBase::Mark mark = m.parser().mark();
auto releaseMark =
mozilla::MakeScopeExit([&m, &mark] { m.parser().release(mark); });
FunctionNode* funNode = nullptr;
unsigned line = 0;
if (!ParseFunction(m, &funNode, &line)) {
return false;
}
if (!CheckFunctionHead(m, funNode)) {
return false;
}
FunctionValidator<Unit> f(m, funNode);
ParseNode* stmtIter = ListHead(FunctionStatementList(funNode));
if (!CheckProcessingDirectives(m, &stmtIter)) {
return false;
}
ValTypeVector args;
if (!CheckArguments(f, &stmtIter, &args)) {
return false;
}
if (!CheckVariables(f, &stmtIter)) {
return false;
}
ParseNode* lastNonEmptyStmt = nullptr;
for (; stmtIter; stmtIter = NextNonEmptyStatement(stmtIter)) {
lastNonEmptyStmt = stmtIter;
if (!CheckStatement(f, stmtIter)) {
return false;
}
}
if (!CheckFinalReturn(f, lastNonEmptyStmt)) {
return false;
}
ModuleValidatorShared::Func* func = nullptr;
if (!CheckFunctionSignature(m, funNode,
FuncType(std::move(args), f.returnedType()),
FunctionName(funNode), &func)) {
return false;
}
if (func->defined()) {
return m.failName(funNode, "function '%s' already defined",
FunctionName(funNode));
}
f.define(func, line);
return true;
}
static bool CheckAllFunctionsDefined(ModuleValidatorShared& m) {
for (unsigned i = 0; i < m.numFuncDefs(); i++) {
const ModuleValidatorShared::Func& f = m.funcDef(i);
if (!f.defined()) {
return m.failNameOffset(f.firstUse(), "missing definition of function %s",
f.name());
}
}
return true;
}
template <typename Unit>
static bool CheckFunctions(ModuleValidator<Unit>& m) {
while (true) {
TokenKind tk;
if (!PeekToken(m.parser(), &tk)) {
return false;
}
if (tk != TokenKind::Function) {
break;
}
if (!CheckFunction(m)) {
return false;
}
}
return CheckAllFunctionsDefined(m);
}
template <typename Unit>
static bool CheckFuncPtrTable(ModuleValidator<Unit>& m, ParseNode* decl) {
if (!decl->isKind(ParseNodeKind::AssignExpr)) {
return m.fail(decl, "function-pointer table must have initializer");
}
AssignmentNode* assignNode = &decl->as<AssignmentNode>();
ParseNode* var = assignNode->left();
if (!var->isKind(ParseNodeKind::Name)) {
return m.fail(var, "function-pointer table name is not a plain name");
}
ParseNode* arrayLiteral = assignNode->right();
if (!arrayLiteral->isKind(ParseNodeKind::ArrayExpr)) {
return m.fail(
var, "function-pointer table's initializer must be an array literal");
}
unsigned length = ListLength(arrayLiteral);
if (!IsPowerOfTwo(length)) {
return m.failf(arrayLiteral,
"function-pointer table length must be a power of 2 (is %u)",
length);
}
unsigned mask = length - 1;
Uint32Vector elemFuncDefIndices;
const FuncType* sig = nullptr;
for (ParseNode* elem = ListHead(arrayLiteral); elem; elem = NextNode(elem)) {
if (!elem->isKind(ParseNodeKind::Name)) {
return m.fail(
elem, "function-pointer table's elements must be names of functions");
}
PropertyName* funcName = elem->as<NameNode>().name();
const ModuleValidatorShared::Func* func = m.lookupFuncDef(funcName);
if (!func) {
return m.fail(
elem, "function-pointer table's elements must be names of functions");
}
const FuncType& funcSig = m.env().types[func->sigIndex()].funcType();
if (sig) {
if (*sig != funcSig) {
return m.fail(elem, "all functions in table must have same signature");
}
} else {
sig = &funcSig;
}
if (!elemFuncDefIndices.append(func->funcDefIndex())) {
return false;
}
}
FuncType copy;
if (!copy.clone(*sig)) {
return false;
}
uint32_t tableIndex;
if (!CheckFuncPtrTableAgainstExisting(m, var, var->as<NameNode>().name(),
std::move(copy), mask, &tableIndex)) {
return false;
}
if (!m.defineFuncPtrTable(tableIndex, std::move(elemFuncDefIndices))) {
return m.fail(var, "duplicate function-pointer definition");
}
return true;
}
template <typename Unit>
static bool CheckFuncPtrTables(ModuleValidator<Unit>& m) {
while (true) {
ParseNode* varStmt;
if (!ParseVarOrConstStatement(m.parser(), &varStmt)) {
return false;
}
if (!varStmt) {
break;
}
for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) {
if (!CheckFuncPtrTable(m, var)) {
return false;
}
}
}
for (unsigned i = 0; i < m.numFuncPtrTables(); i++) {
ModuleValidatorShared::Table& table = m.table(i);
if (!table.defined()) {
return m.failNameOffset(table.firstUse(),
"function-pointer table %s wasn't defined",
table.name());
}
}
return true;
}
static bool CheckModuleExportFunction(ModuleValidatorShared& m, ParseNode* pn,
PropertyName* maybeFieldName = nullptr) {
if (!pn->isKind(ParseNodeKind::Name)) {
return m.fail(pn, "expected name of exported function");
}
PropertyName* funcName = pn->as<NameNode>().name();
const ModuleValidatorShared::Func* func = m.lookupFuncDef(funcName);
if (!func) {
return m.failName(pn, "function '%s' not found", funcName);
}
return m.addExportField(*func, maybeFieldName);
}
static bool CheckModuleExportObject(ModuleValidatorShared& m,
ParseNode* object) {
MOZ_ASSERT(object->isKind(ParseNodeKind::ObjectExpr));
for (ParseNode* pn = ListHead(object); pn; pn = NextNode(pn)) {
if (!IsNormalObjectField(pn)) {
return m.fail(pn,
"only normal object properties may be used in the export "
"object literal");
}
PropertyName* fieldName = ObjectNormalFieldName(pn);
ParseNode* initNode = ObjectNormalFieldInitializer(pn);
if (!initNode->isKind(ParseNodeKind::Name)) {
return m.fail(
initNode,
"initializer of exported object literal must be name of function");
}
if (!CheckModuleExportFunction(m, initNode, fieldName)) {
return false;
}
}
return true;
}
template <typename Unit>
static bool CheckModuleReturn(ModuleValidator<Unit>& m) {
TokenKind tk;
if (!GetToken(m.parser(), &tk)) {
return false;
}
auto& ts = m.parser().tokenStream;
if (tk != TokenKind::Return) {
return m.failCurrentOffset(
(tk == TokenKind::RightCurly || tk == TokenKind::Eof)
? "expecting return statement"
: "invalid asm.js. statement");
}
ts.anyCharsAccess().ungetToken();
ParseNode* returnStmt = m.parser().statementListItem(YieldIsName);
if (!returnStmt) {
return false;
}
ParseNode* returnExpr = ReturnExpr(returnStmt);
if (!returnExpr) {
return m.fail(returnStmt, "export statement must return something");
}
if (returnExpr->isKind(ParseNodeKind::ObjectExpr)) {
if (!CheckModuleExportObject(m, returnExpr)) {
return false;
}
} else {
if (!CheckModuleExportFunction(m, returnExpr)) {
return false;
}
}
return true;
}
template <typename Unit>
static bool CheckModuleEnd(ModuleValidator<Unit>& m) {
TokenKind tk;
if (!GetToken(m.parser(), &tk)) {
return false;
}
if (tk != TokenKind::Eof && tk != TokenKind::RightCurly) {
return m.failCurrentOffset(
"top-level export (return) must be the last statement");
}
m.parser().tokenStream.anyCharsAccess().ungetToken();
return true;
}
template <typename Unit>
static SharedModule CheckModule(JSContext* cx, AsmJSParser<Unit>& parser,
ParseNode* stmtList, unsigned* time) {
int64_t before = PRMJ_Now();
FunctionNode* moduleFunctionNode = parser.pc_->functionBox()->functionNode;
ModuleValidator<Unit> m(cx, parser, moduleFunctionNode);
if (!m.init()) {
return nullptr;
}
if (!CheckFunctionHead(m, moduleFunctionNode)) {
return nullptr;
}
if (!CheckModuleArguments(m, moduleFunctionNode)) {
return nullptr;
}
if (!CheckPrecedingStatements(m, stmtList)) {
return nullptr;
}
if (!CheckModuleProcessingDirectives(m)) {
return nullptr;
}
if (!CheckModuleGlobals(m)) {
return nullptr;
}
if (!m.startFunctionBodies()) {
return nullptr;
}
if (!CheckFunctions(m)) {
return nullptr;
}
if (!CheckFuncPtrTables(m)) {
return nullptr;
}
if (!CheckModuleReturn(m)) {
return nullptr;
}
if (!CheckModuleEnd(m)) {
return nullptr;
}
SharedModule module = m.finish();
if (!module) {
return nullptr;
}
*time = (PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC;
return module;
}
static bool LinkFail(JSContext* cx, const char* str) {
JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage,
nullptr, JSMSG_USE_ASM_LINK_FAIL, str);
return false;
}
static bool IsMaybeWrappedScriptedProxy(JSObject* obj) {
JSObject* unwrapped = UncheckedUnwrap(obj);
return unwrapped && IsScriptedProxy(unwrapped);
}
static bool GetDataProperty(JSContext* cx, HandleValue objVal, HandleAtom field,
MutableHandleValue v) {
if (!objVal.isObject()) {
return LinkFail(cx, "accessing property of non-object");
}
RootedObject obj(cx, &objVal.toObject());
if (IsMaybeWrappedScriptedProxy(obj)) {
return LinkFail(cx, "accessing property of a Proxy");
}
Rooted<PropertyDescriptor> desc(cx);
RootedId id(cx, AtomToId(field));
if (!GetPropertyDescriptor(cx, obj, id, &desc)) {
return false;
}
if (!desc.object()) {
return LinkFail(cx, "property not present on object");
}
if (!desc.isDataDescriptor()) {
return LinkFail(cx, "property is not a data property");
}
v.set(desc.value());
return true;
}
static bool GetDataProperty(JSContext* cx, HandleValue objVal,
const char* fieldChars, MutableHandleValue v) {
RootedAtom field(cx, AtomizeUTF8Chars(cx, fieldChars, strlen(fieldChars)));
if (!field) {
return false;
}
return GetDataProperty(cx, objVal, field, v);
}
static bool GetDataProperty(JSContext* cx, HandleValue objVal,
ImmutablePropertyNamePtr field,
MutableHandleValue v) {
HandlePropertyName fieldHandle = field;
return GetDataProperty(cx, objVal, fieldHandle, v);
}
static bool HasObjectValueOfMethodPure(JSObject* obj, JSContext* cx) {
Value v;
if (!GetPropertyPure(cx, obj, NameToId(cx->names().valueOf), &v)) {
return false;
}
JSFunction* fun;
if (!IsFunctionObject(v, &fun)) {
return false;
}
return IsSelfHostedFunctionWithName(fun, cx->names().Object_valueOf);
}
static bool HasPureCoercion(JSContext* cx, HandleValue v) {
if (v.toObject().is<JSFunction>() &&
HasNoToPrimitiveMethodPure(&v.toObject(), cx) &&
HasObjectValueOfMethodPure(&v.toObject(), cx) &&
HasNativeMethodPure(&v.toObject(), cx->names().toString, fun_toString,
cx)) {
return true;
}
return false;
}
static bool ValidateGlobalVariable(JSContext* cx, const AsmJSGlobal& global,
HandleValue importVal,
Maybe<LitValPOD>* val) {
switch (global.varInitKind()) {
case AsmJSGlobal::InitConstant:
val->emplace(global.varInitVal());
return true;
case AsmJSGlobal::InitImport: {
RootedValue v(cx);
if (!GetDataProperty(cx, importVal, global.field(), &v)) {
return false;
}
if (!v.isPrimitive() && !HasPureCoercion(cx, v)) {
return LinkFail(cx, "Imported values must be primitives");
}
switch (global.varInitImportType().code()) {
case ValType::I32: {
int32_t i32;
if (!ToInt32(cx, v, &i32)) {
return false;
}
val->emplace(uint32_t(i32));
return true;
}
case ValType::I64:
MOZ_CRASH("int64");
case ValType::F32: {
float f;
if (!RoundFloat32(cx, v, &f)) {
return false;
}
val->emplace(f);
return true;
}
case ValType::F64: {
double d;
if (!ToNumber(cx, v, &d)) {
return false;
}
val->emplace(d);
return true;
}
case ValType::Ref:
case ValType::NullRef:
case ValType::AnyRef: {
MOZ_CRASH("not available in asm.js");
}
}
}
}
MOZ_CRASH("unreachable");
}
static bool ValidateFFI(JSContext* cx, const AsmJSGlobal& global,
HandleValue importVal,
MutableHandle<FunctionVector> ffis) {
RootedValue v(cx);
if (!GetDataProperty(cx, importVal, global.field(), &v)) {
return false;
}
if (!IsFunctionObject(v)) {
return LinkFail(cx, "FFI imports must be functions");
}
ffis[global.ffiIndex()].set(&v.toObject().as<JSFunction>());
return true;
}
static bool ValidateArrayView(JSContext* cx, const AsmJSGlobal& global,
HandleValue globalVal) {
if (!global.field()) {
return true;
}
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, global.field(), &v)) {
return false;
}
bool tac = IsTypedArrayConstructor(v, global.viewType());
if (!tac) {
return LinkFail(cx, "bad typed array constructor");
}
return true;
}
static bool ValidateMathBuiltinFunction(JSContext* cx,
const AsmJSGlobal& global,
HandleValue globalVal) {
RootedValue v(cx);
if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) {
return false;
}
if (!GetDataProperty(cx, v, global.field(), &v)) {
return false;
}
Native native = nullptr;
switch (global.mathBuiltinFunction()) {
case AsmJSMathBuiltin_sin:
native = math_sin;
break;
case AsmJSMathBuiltin_cos:
native = math_cos;
break;
case AsmJSMathBuiltin_tan:
native = math_tan;
break;
case AsmJSMathBuiltin_asin:
native = math_asin;
break;
case AsmJSMathBuiltin_acos:
native = math_acos;
break;
case AsmJSMathBuiltin_atan:
native = math_atan;
break;
case AsmJSMathBuiltin_ceil:
native = math_ceil;
break;
case AsmJSMathBuiltin_floor:
native = math_floor;
break;
case AsmJSMathBuiltin_exp:
native = math_exp;
break;
case AsmJSMathBuiltin_log:
native = math_log;
break;
case AsmJSMathBuiltin_pow:
native = math_pow;
break;
case AsmJSMathBuiltin_sqrt:
native = math_sqrt;
break;
case AsmJSMathBuiltin_min:
native = math_min;
break;
case AsmJSMathBuiltin_max:
native = math_max;
break;
case AsmJSMathBuiltin_abs:
native = math_abs;
break;
case AsmJSMathBuiltin_atan2:
native = math_atan2;
break;
case AsmJSMathBuiltin_imul:
native = math_imul;
break;
case AsmJSMathBuiltin_clz32:
native = math_clz32;
break;
case AsmJSMathBuiltin_fround:
native = math_fround;
break;
}
if (!IsNativeFunction(v, native)) {
return LinkFail(cx, "bad Math.* builtin function");
}
return true;
}
static bool ValidateConstant(JSContext* cx, const AsmJSGlobal& global,
HandleValue globalVal) {
RootedValue v(cx, globalVal);
if (global.constantKind() == AsmJSGlobal::MathConstant) {
if (!GetDataProperty(cx, v, cx->names().Math, &v)) {
return false;
}
}
if (!GetDataProperty(cx, v, global.field(), &v)) {
return false;
}
if (!v.isNumber()) {
return LinkFail(cx, "math / global constant value needs to be a number");
}
if (IsNaN(global.constantValue())) {
if (!IsNaN(v.toNumber())) {
return LinkFail(cx, "global constant value needs to be NaN");
}
} else {
if (v.toNumber() != global.constantValue()) {
return LinkFail(cx, "global constant value mismatch");
}
}
return true;
}
static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata,
HandleValue bufferVal,
MutableHandle<ArrayBufferObjectMaybeShared*> buffer) {
if (metadata.memoryUsage == MemoryUsage::Shared) {
if (!IsSharedArrayBuffer(bufferVal)) {
return LinkFail(
cx, "shared views can only be constructed onto SharedArrayBuffer");
}
} else {
if (!IsArrayBuffer(bufferVal)) {
return LinkFail(
cx, "unshared views can only be constructed onto ArrayBuffer");
}
}
buffer.set(&AsAnyArrayBuffer(bufferVal));
uint32_t memoryLength = buffer->byteLength();
if (!IsValidAsmJSHeapLength(memoryLength)) {
UniqueChars msg(JS_smprintf(
"ArrayBuffer byteLength 0x%x is not a valid heap length. The next "
"valid length is 0x%x",
memoryLength, RoundUpToNextValidAsmJSHeapLength(memoryLength)));
if (!msg) {
return false;
}
return LinkFail(cx, msg.get());
}
MOZ_ASSERT((metadata.minMemoryLength - 1) <= INT32_MAX);
if (memoryLength < metadata.minMemoryLength) {
UniqueChars msg(JS_smprintf(
"ArrayBuffer byteLength of 0x%x is less than 0x%x (the size implied "
"by const heap accesses).",
memoryLength, metadata.minMemoryLength));
if (!msg) {
return false;
}
return LinkFail(cx, msg.get());
}
if (buffer->is<ArrayBufferObject>()) {
Rooted<ArrayBufferObject*> arrayBuffer(cx,
&buffer->as<ArrayBufferObject>());
if (!arrayBuffer->prepareForAsmJS()) {
return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
}
} else {
return LinkFail(cx, "Unable to prepare SharedArrayBuffer for asm.js use");
}
MOZ_ASSERT(buffer->isPreparedForAsmJS());
return true;
}
static bool GetImports(JSContext* cx, const AsmJSMetadata& metadata,
HandleValue globalVal, HandleValue importVal,
MutableHandle<FunctionVector> funcImports,
MutableHandleValVector valImports) {
Rooted<FunctionVector> ffis(cx, FunctionVector(cx));
if (!ffis.resize(metadata.numFFIs)) {
return false;
}
for (const AsmJSGlobal& global : metadata.asmJSGlobals) {
switch (global.which()) {
case AsmJSGlobal::Variable: {
Maybe<LitValPOD> litVal;
if (!ValidateGlobalVariable(cx, global, importVal, &litVal)) {
return false;
}
if (!valImports.append(Val(litVal->asLitVal()))) {
return false;
}
break;
}
case AsmJSGlobal::FFI:
if (!ValidateFFI(cx, global, importVal, &ffis)) {
return false;
}
break;
case AsmJSGlobal::ArrayView:
case AsmJSGlobal::ArrayViewCtor:
if (!ValidateArrayView(cx, global, globalVal)) {
return false;
}
break;
case AsmJSGlobal::MathBuiltinFunction:
if (!ValidateMathBuiltinFunction(cx, global, globalVal)) {
return false;
}
break;
case AsmJSGlobal::Constant:
if (!ValidateConstant(cx, global, globalVal)) {
return false;
}
break;
}
}
for (const AsmJSImport& import : metadata.asmJSImports) {
if (!funcImports.append(ffis[import.ffiIndex()])) {
return false;
}
}
return true;
}
static bool TryInstantiate(JSContext* cx, CallArgs args, const Module& module,
const AsmJSMetadata& metadata,
MutableHandleWasmInstanceObject instanceObj,
MutableHandleObject exportObj) {
HandleValue globalVal = args.get(0);
HandleValue importVal = args.get(1);
HandleValue bufferVal = args.get(2);
if (!HasCompilerSupport(cx)) {
return LinkFail(cx, "no compiler support");
}
RootedArrayBufferObjectMaybeShared buffer(cx);
RootedWasmMemoryObject memory(cx);
if (module.metadata().usesMemory()) {
if (!CheckBuffer(cx, metadata, bufferVal, &buffer)) {
return false;
}
memory = WasmMemoryObject::create(cx, buffer, nullptr);
if (!memory) {
return false;
}
}
RootedValVector valImports(cx);
Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
if (!GetImports(cx, metadata, globalVal, importVal, &funcs, &valImports)) {
return false;
}
Rooted<WasmGlobalObjectVector> globalObjs(cx);
Rooted<WasmTableObjectVector> tables(cx);
if (!module.instantiate(cx, funcs, tables.get(), memory, valImports,
globalObjs.get(), nullptr, instanceObj))
return false;
exportObj.set(&instanceObj->exportsObj());
return true;
}
static bool HandleInstantiationFailure(JSContext* cx, CallArgs args,
const AsmJSMetadata& metadata) {
RootedAtom name(cx, args.callee().as<JSFunction>().explicitName());
if (cx->isExceptionPending()) {
return false;
}
ScriptSource* source = metadata.scriptSource.get();
bool haveSource = source->hasSourceText();
if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) {
return false;
}
if (!haveSource) {
JS_ReportErrorASCII(cx,
"asm.js link failure with source discarding enabled");
return false;
}
uint32_t begin = metadata.toStringStart;
uint32_t end = metadata.srcEndAfterCurly();
Rooted<JSFlatString*> src(cx, source->substringDontDeflate(cx, begin, end));
if (!src) {
return false;
}
RootedFunction fun(
cx, NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL, name,
nullptr, gc::AllocKind::FUNCTION,
TenuredObject));
if (!fun) {
return false;
}
JS::CompileOptions options(cx);
options.setMutedErrors(source->mutedErrors())
.setFile(source->filename())
.setNoScriptRval(false);
options.asmJSOption = AsmJSOption::Disabled;
if (metadata.strict) {
options.strictOption = true;
}
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, src)) {
return false;
}
SourceText<char16_t> srcBuf;
const char16_t* chars = stableChars.twoByteRange().begin().get();
SourceOwnership ownership = stableChars.maybeGiveOwnershipToCaller()
? SourceOwnership::TakeOwnership
: SourceOwnership::Borrowed;
if (!srcBuf.init(cx, chars, end - begin, ownership)) {
return false;
}
if (!frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf,
Nothing())) {
return false;
}
args.setCallee(ObjectValue(*fun));
return InternalCallOrConstruct(
cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT);
}
static const Module& AsmJSModuleFunctionToModule(JSFunction* fun) {
MOZ_ASSERT(IsAsmJSModule(fun));
const Value& v = fun->getExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT);
return v.toObject().as<WasmModuleObject>().module();
}
bool js::InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
JSFunction* callee = &args.callee().as<JSFunction>();
const Module& module = AsmJSModuleFunctionToModule(callee);
const AsmJSMetadata& metadata = module.metadata().asAsmJS();
RootedWasmInstanceObject instanceObj(cx);
RootedObject exportObj(cx);
if (!TryInstantiate(cx, args, module, metadata, &instanceObj, &exportObj)) {
return HandleInstantiationFailure(cx, args, metadata);
}
args.rval().set(ObjectValue(*exportObj));
return true;
}
static JSFunction* NewAsmJSModuleFunction(JSContext* cx, JSFunction* origFun,
HandleObject moduleObj) {
RootedAtom name(cx, origFun->explicitName());
JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR
: JSFunction::ASMJS_CTOR;
JSFunction* moduleFun = NewNativeConstructor(
cx, InstantiateAsmJS, origFun->nargs(), name,
gc::AllocKind::FUNCTION_EXTENDED, TenuredObject, flags);
if (!moduleFun) {
return nullptr;
}
moduleFun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT,
ObjectValue(*moduleObj));
MOZ_ASSERT(IsAsmJSModule(moduleFun));
return moduleFun;
}
static bool NoExceptionPending(JSContext* cx) {
return cx->helperThread() || !cx->isExceptionPending();
}
static bool SuccessfulValidation(frontend::ParserBase& parser,
unsigned compilationTime) {
constexpr unsigned errNum =
#ifdef JS_MORE_DETERMINISTIC
JSMSG_USE_ASM_TYPE_OK_NO_TIME
#else
JSMSG_USE_ASM_TYPE_OK
#endif
;
char timeChars[20];
SprintfLiteral(timeChars, "%u", compilationTime);
return parser.warningNoOffset(errNum, timeChars);
}
static bool TypeFailureWarning(frontend::ParserBase& parser, const char* str) {
if (parser.options().throwOnAsmJSValidationFailureOption) {
parser.errorNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
return false;
}
Unused << parser.warningNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : "");
return false;
}
static bool EstablishPreconditions(JSContext* cx,
frontend::ParserBase& parser) {
if (!HasCompilerSupport(cx) || !IonCanCompile()) {
return TypeFailureWarning(parser, "Disabled by lack of compiler support");
}
switch (parser.options().asmJSOption) {
case AsmJSOption::Disabled:
return TypeFailureWarning(parser, "Disabled by 'asmjs' runtime option");
case AsmJSOption::DisabledByDebugger:
return TypeFailureWarning(parser, "Disabled by debugger");
case AsmJSOption::Enabled:
break;
}
if (parser.pc_->isGenerator()) {
return TypeFailureWarning(parser, "Disabled by generator context");
}
if (parser.pc_->isAsync()) {
return TypeFailureWarning(parser, "Disabled by async context");
}
if (parser.pc_->isArrowFunction()) {
return TypeFailureWarning(parser, "Disabled by arrow function context");
}
if (parser.pc_->isMethod() || parser.pc_->isGetterOrSetter()) {
return TypeFailureWarning(
parser, "Disabled by class constructor or method context");
}
return true;
}
template <typename Unit>
static bool DoCompileAsmJS(JSContext* cx, AsmJSParser<Unit>& parser,
ParseNode* stmtList, bool* validated) {
*validated = false;
if (!EstablishPreconditions(cx, parser)) {
return NoExceptionPending(cx);
}
unsigned time;
SharedModule module = CheckModule(cx, parser, stmtList, &time);
if (!module) {
return NoExceptionPending(cx);
}
Rooted<WasmModuleObject*> moduleObj(cx,
WasmModuleObject::create(cx, *module));
if (!moduleObj) {
return false;
}
FunctionBox* funbox = parser.pc_->functionBox();
RootedFunction moduleFun(
cx, NewAsmJSModuleFunction(cx, funbox->function(), moduleObj));
if (!moduleFun) {
return false;
}
MOZ_ASSERT(funbox->function()->isInterpreted());
funbox->clobberFunction(moduleFun);
*validated = true;
SuccessfulValidation(parser, time);
return NoExceptionPending(cx);
}
bool js::CompileAsmJS(JSContext* cx, AsmJSParser<char16_t>& parser,
ParseNode* stmtList, bool* validated) {
return DoCompileAsmJS(cx, parser, stmtList, validated);
}
bool js::CompileAsmJS(JSContext* cx, AsmJSParser<Utf8Unit>& parser,
ParseNode* stmtList, bool* validated) {
return DoCompileAsmJS(cx, parser, stmtList, validated);
}
bool js::IsAsmJSModuleNative(Native native) {
return native == InstantiateAsmJS;
}
bool js::IsAsmJSModule(JSFunction* fun) {
return fun->maybeNative() == InstantiateAsmJS;
}
bool js::IsAsmJSFunction(JSFunction* fun) {
if (IsExportedFunction(fun)) {
return ExportedFunctionToInstance(fun).metadata().isAsmJS();
}
return false;
}
bool js::IsAsmJSStrictModeModuleOrFunction(JSFunction* fun) {
if (IsAsmJSModule(fun)) {
return AsmJSModuleFunctionToModule(fun).metadata().asAsmJS().strict;
}
if (IsAsmJSFunction(fun)) {
return ExportedFunctionToInstance(fun).metadata().asAsmJS().strict;
}
return false;
}
bool js::IsAsmJSCompilationAvailable(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool available =
HasCompilerSupport(cx) && IonCanCompile() && cx->options().asmJS();
args.rval().set(BooleanValue(available));
return true;
}
static JSFunction* MaybeWrappedNativeFunction(const Value& v) {
if (!v.isObject()) {
return nullptr;
}
return v.toObject().maybeUnwrapIf<JSFunction>();
}
bool js::IsAsmJSModule(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool rval = false;
if (JSFunction* fun = MaybeWrappedNativeFunction(args.get(0))) {
rval = IsAsmJSModule(fun);
}
args.rval().set(BooleanValue(rval));
return true;
}
bool js::IsAsmJSFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
bool rval = false;
if (JSFunction* fun = MaybeWrappedNativeFunction(args.get(0))) {
rval = IsAsmJSFunction(fun);
}
args.rval().set(BooleanValue(rval));
return true;
}
JSString* js::AsmJSModuleToString(JSContext* cx, HandleFunction fun,
bool isToSource) {
MOZ_ASSERT(IsAsmJSModule(fun));
const AsmJSMetadata& metadata =
AsmJSModuleFunctionToModule(fun).metadata().asAsmJS();
uint32_t begin = metadata.toStringStart;
uint32_t end = metadata.srcEndAfterCurly();
ScriptSource* source = metadata.scriptSource.get();
StringBuffer out(cx);
if (isToSource && fun->isLambda() && !out.append("(")) {
return nullptr;
}
bool haveSource = source->hasSourceText();
if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) {
return nullptr;
}
if (!haveSource) {
if (!out.append("function ")) {
return nullptr;
}
if (fun->explicitName() && !out.append(fun->explicitName())) {
return nullptr;
}
if (!out.append("() {\n [native code]\n}")) {
return nullptr;
}
} else {
Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
if (!src) {
return nullptr;
}
if (!out.append(src)) {
return nullptr;
}
}
if (isToSource && fun->isLambda() && !out.append(")")) {
return nullptr;
}
return out.finishString();
}
JSString* js::AsmJSFunctionToString(JSContext* cx, HandleFunction fun) {
MOZ_ASSERT(IsAsmJSFunction(fun));
const AsmJSMetadata& metadata =
ExportedFunctionToInstance(fun).metadata().asAsmJS();
const AsmJSExport& f =
metadata.lookupAsmJSExport(ExportedFunctionToFuncIndex(fun));
uint32_t begin = metadata.srcStart + f.startOffsetInModule();
uint32_t end = metadata.srcStart + f.endOffsetInModule();
ScriptSource* source = metadata.scriptSource.get();
StringBuffer out(cx);
if (!out.append("function ")) {
return nullptr;
}
bool haveSource = source->hasSourceText();
if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) {
return nullptr;
}
if (!haveSource) {
MOZ_ASSERT(fun->explicitName());
if (!out.append(fun->explicitName())) {
return nullptr;
}
if (!out.append("() {\n [native code]\n}")) {
return nullptr;
}
} else {
Rooted<JSFlatString*> src(cx, source->substring(cx, begin, end));
if (!src) {
return nullptr;
}
if (!out.append(src)) {
return nullptr;
}
}
return out.finishString();
}
bool js::IsValidAsmJSHeapLength(uint32_t length) {
if (length < MinHeapLength) {
return false;
}
return wasm::IsValidARMImmediate(length);
}