using JS::AsmJSOption; using JS::AutoStableStringChars; using JS::GenericNaN; using JS::SourceText; using mozilla::Abs; using mozilla::AsVariant; using mozilla::CeilingLog2; using mozilla::HashGeneric; using mozilla::IsNegativeZero; using mozilla::IsPositiveZero; using mozilla::Maybe; using mozilla::Nothing; using mozilla::PodZero; using mozilla::PositiveInfinity; using mozilla::Some; using mozilla::Utf8Unit; using mozilla::Compression::LZ4;
// The asm.js valid heap lengths are precisely the WASM valid heap lengths for // ARM greater or equal to MinHeapLength staticconst size_t MinHeapLength = StandardPageSizeBytes; // An asm.js heap can in principle be up to INT32_MAX bytes but requirements // on the format restrict it further to the largest pseudo-ARM-immediate. // See IsValidAsmJSHeapLength(). staticconst uint64_t MaxHeapLength = 0x7f000000;
// Because ARM has a fixed-width instruction encoding, ARM can only express a // limited subset of immediates (in a single instruction). staticconst uint64_t HighestValidARMImmediate = 0xff000000;
// Heap length on ARM should fit in an ARM immediate. We approximate the set // of valid ARM immediates with the predicate: // 2^n for n in [16, 24) // or // 2^24 * n for n >= 1. staticbool IsValidARMImmediate(uint32_t i) { bool valid = (std::has_single_bit(i) || (i & 0x00ffffff) == 0);
MOZ_ASSERT_IF(valid, i % StandardPageSizeBytes == 0);
return valid;
}
static uint64_t RoundUpToNextValidARMImmediate(uint64_t i) {
MOZ_ASSERT(i <= HighestValidARMImmediate);
static_assert(HighestValidARMImmediate == 0xff000000, "algorithm relies on specific constant");
if (i <= 16 * 1024 * 1024) {
i = i ? mozilla::RoundUpPow2(i) : 0;
} else {
i = (i + 0x00ffffff) & ~0x00ffffff;
}
public:
AsmJSGlobal() = default;
AsmJSGlobal(Which which, UniqueChars field) {
mozilla::PodZero(&pod); // zero padding for Valgrind
pod.which_ = which;
field_ = std::move(field);
} constchar* 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_;
} // When a view is created from an imported constructor: // var I32 = stdlib.Int32Array; // var i32 = new I32(buffer); // the second import has nothing to validate and thus has a null field.
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_;
}
};
using AsmJSGlobalVector = Vector<AsmJSGlobal, 0, SystemAllocPolicy>;
// An AsmJSImport is slightly different than an asm.js FFI function: a single // asm.js FFI function can be called with many different signatures. When // compiled to wasm, each unique FFI function paired with signature generates a // wasm import. class AsmJSImport {
uint32_t ffiIndex_;
using AsmJSImportVector = Vector<AsmJSImport, 0, SystemAllocPolicy>;
// An AsmJSExport logically extends Export with the extra information needed for // an asm.js exported function, viz., the offsets in module's source chars in // case the function is toString()ed. class AsmJSExport {
uint32_t funcIndex_ = 0;
// All fields are treated as cacheable POD:
uint32_t startOffsetInModule_ = 0; // Store module-start-relative offsets
uint32_t endOffsetInModule_ = 0; // so preserved by serialization.
using AsmJSExportVector = Vector<AsmJSExport, 0, SystemAllocPolicy>;
// Holds the immutable guts of an AsmJSModule. // // CodeMetadataForAsmJSImpl is built incrementally by ModuleValidator and then // shared immutably between AsmJSModules.
// These values are not serialized since they are relative to the // containing script which can be different between serialization and // deserialization contexts. Thus, they must be set explicitly using the // ambient Parser/ScriptSource after deserialization. // // srcStart refers to the offset in the ScriptSource to the beginning of // the asm.js module function. If the function has been created with the // Function constructor, this will be the first character in the function // source. Otherwise, it will be the opening parenthesis of the arguments // list.
uint32_t toStringStart;
uint32_t srcStart; bool strict; bool alwaysUseFdlibm = false;
RefPtr<ScriptSource> source;
const AsmJSExport& lookupAsmJSExport(uint32_t funcIndex) const { // The AsmJSExportVector isn't stored in sorted order so do a linear // search. This is for the super-cold and already-expensive toString() // path and the number of exports is generally small. for (const AsmJSExport& exp : asmJSExports) { if (exp.funcIndex() == funcIndex) { return exp;
}
}
MOZ_CRASH("missing asm.js func export");
}
// The number of formals is equal to the number of parameters (excluding the // trailing lexical scope). There are no destructuring or rest parameters for // asm.js functions.
*numFormals = argsBody->count();
// If the function has been fully parsed, the trailing function body node is a // lexical scope. If we've only parsed the function parameters, the last node // is the last parameter. if (*numFormals > 0 && argsBody->last()->is<LexicalScopeNode>()) {
MOZ_ASSERT(argsBody->last()->as<LexicalScopeNode>().scopeBody()->isKind(
ParseNodeKind::StatementList));
(*numFormals)--;
}
return argsBody->head();
}
staticinline ParseNode* FunctionStatementList(FunctionNode* funNode) {
LexicalScopeNode* last = funNode->body()->body();
MOZ_ASSERT(last->isEmptyScope());
ParseNode* body = last->scopeBody();
MOZ_ASSERT(body->isKind(ParseNodeKind::StatementList)); return body;
}
// Represents the type and value of an asm.js numeric literal. // // A literal is a double iff the literal contains a decimal point (even if the // fractional part is 0). Otherwise, integers may be classified: // fixnum: [0, 2^31) // negative int: [-2^31, 0) // big unsigned: [2^31, 2^32) // out of range: otherwise // Lastly, a literal may be a float literal which is any double or integer // literal coerced with Math.fround. class NumLit { public: enum Which {
Fixnum,
NegativeInt,
BigUnsigned, Double, Float,
OutOfRangeInt = -1
};
private:
Which which_ = OutOfRangeInt;
JS::Value value_;
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");
} returnfalse;
}
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");
}
};
// Represents the type of a general asm.js expression. // // A canonical subset of types representing the coercion targets: Int, Float, // Double. // // Void is also part of the canonical subset.
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
};
// Map an already canonicalized Type to the return type of a function call. static Type ret(Type t) {
MOZ_ASSERT(t.isCanonical()); // The 32-bit external type is Signed, not Int. 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;
}
// Map |t| to one of the canonical vartype representations of a // wasm::ValType. static Type canonicalize(Type t) { switch (t.which()) { case Fixnum: caseSigned: caseUnsigned: caseInt: returnInt;
caseFloat: returnFloat;
case DoubleLit: caseDouble: returnDouble;
caseVoid: returnVoid;
case MaybeDouble: case MaybeFloat: case Floatish: case Intish: // These types need some kind of coercion, they can't be mapped // to an VarType. break;
}
MOZ_CRASH("Invalid vartype");
}
// Check if this is one of the valid types for a function argument. bool isArgType() const { return isInt() || isFloat() || isDouble(); }
// Check if this is one of the valid types for a function return value. bool isReturnType() const { return isSigned() || isFloat() || isDouble() || isVoid();
}
// Check if this is one of the valid types for a global variable. bool isGlobalVarType() const { return isArgType(); }
// Check if this is one of the canonical vartype representations of a // wasm::ValType, or is void. See Type::canonicalize(). bool isCanonical() const { switch (which()) { caseInt: caseFloat: caseDouble: caseVoid: returntrue; default: returnfalse;
}
}
// Check if this is a canonical representation of a wasm::ValType. bool isCanonicalValType() const { return !isVoid() && isCanonical(); }
// Convert this canonical type to a wasm::ValType.
ValType canonicalToValType() const { switch (which()) { caseInt: return ValType::I32; caseFloat: return ValType::F32; caseDouble: return ValType::F64; default:
MOZ_CRASH("Need canonical type");
}
}
// Convert this type to a wasm::TypeCode for use in a wasm // block signature. This works for all types, including non-canonical // ones. Consequently, the type isn't valid for subsequent asm.js // validation; it's only valid for use in producing wasm.
TypeCode toWasmBlockSignatureType() const { switch (which()) { case Fixnum: caseSigned: caseUnsigned: caseInt: case Intish: return TypeCode::I32;
caseFloat: case MaybeFloat: case Floatish: return TypeCode::F32;
case DoubleLit: caseDouble: case MaybeDouble: return TypeCode::F64;
VarOrConst(unsigned index, Type::Which which)
: type_(which), index_(index) { // The |literalValue_| field remains unused and // uninitialized for non-constant variables.
}
explicit VarOrConst(double constant)
: type_(Type::Double),
literalValue_(NumLit::Double, DoubleValue(constant)) { // The index_ field is unused and uninitialized for // constant doubles.
}
} varOrConst;
uint32_t funcDefIndex_;
uint32_t tableIndex_;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
// |varOrConst|, through |varOrConst.literalValue_|, has a // non-trivial constructor and therefore MUST be placement-new'd // into existence.
MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
U() : funcDefIndex_(0) {}
MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
} u;
// State used to build the AsmJSModule in finish():
CompilerEnvironment compilerEnv_;
MutableModuleMetadata moduleMeta_;
MutableCodeMetadata codeMeta_;
MutableCodeMetadataForAsmJSImpl codeMetaForAsmJS_;
if (!arrayViews_.append(ArrayView(var, vt))) { returnfalse;
}
Global* global = validationLifo_.new_<Global>(Global::ArrayView); if (!global) { returnfalse;
} new (&global->u.viewType_) Scalar::Type(vt); if (!globalMap_.putNew(var, global)) { returnfalse;
}
Global* global = validationLifo_.new_<Global>(Global::MathBuiltinFunction); if (!global) { returnfalse;
} new (&global->u.mathBuiltinFunc_) AsmJSMathBuiltinFunction(func); if (!globalMap_.putNew(var, global)) { returnfalse;
}
Global* global = validationLifo_.new_<Global>(Global::ArrayViewCtor); if (!global) { returnfalse;
} new (&global->u.viewType_) Scalar::Type(vt); if (!globalMap_.putNew(var, global)) { returnfalse;
}
Global* global = validationLifo_.new_<Global>(Global::FFI); if (!global) { returnfalse;
} new (&global->u.ffiIndex_) uint32_t(ffiIndex); if (!globalMap_.putNew(var, global)) { returnfalse;
}
AsmJSGlobal g(AsmJSGlobal::FFI, std::move(fieldChars));
g.pod.u.ffiIndex_ = ffiIndex; return codeMetaForAsmJS_->asmJSGlobals.append(std::move(g));
} bool addExportField(const Func& func, TaggedParserAtomIndex maybeField) { // Record the field name of this export.
CacheableName fieldName; if (maybeField) {
UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField); if (!fieldChars) { returnfalse;
}
fieldName = CacheableName::fromUTF8Chars(std::move(fieldChars));
}
// Declare which function is exported which gives us an index into the // module ExportVector.
uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex(); if (!moduleMeta_->exports.emplaceBack(std::move(fieldName), funcIndex,
DefinitionKind::Function)) { returnfalse;
}
// The exported function might have already been exported in which case // the index will refer into the range of AsmJSExports. return codeMetaForAsmJS_->asmJSExports.emplaceBack(
funcIndex, func.srcBegin() - codeMetaForAsmJS_->srcStart,
func.srcEnd() - codeMetaForAsmJS_->srcStart);
}
// The ModuleValidator encapsulates the entire validation of an asm.js module. // Its lifetime goes from the validation of the top components of an asm.js // module (all the globals), the emission of bytecode for all the functions in // the module and the validation of function's pointer tables. It also finishes // the compilation of all the module's stubs. template <typename Unit> class MOZ_STACK_CLASS ModuleValidator : public ModuleValidatorShared { private:
AsmJSParser<Unit>& parser_;
auto& ts = tokenStream();
ErrorMetadata metadata; if (ts.computeErrorMetadata(&metadata, AsVariant(offset))) { if (ts.anyCharsAccess().options().throwOnAsmJSValidationFailure()) {
ReportCompileErrorLatin1VA(fc_, std::move(metadata), nullptr,
JSMSG_USE_ASM_TYPE_FAIL, &args);
} else { // asm.js type failure is indicated by calling one of the fail* // functions below. These functions always return false to // halt asm.js parsing. Whether normal parsing is attempted as // fallback, depends whether an exception is also set. // // If warning succeeds, no exception is set. If warning fails, // an exception is set and execution will halt. Thus it's safe // and correct to ignore the return value here.
(void)ts.compileWarning(std::move(metadata), nullptr,
JSMSG_USE_ASM_TYPE_FAIL, &args);
}
}
// asm.js does not have any wasm bytecode to save; view-source is // provided through the ScriptSource.
BytecodeBufferOrSource bytecode;
if (!moduleMeta_->prepareForCompile(compilerEnv_.mode())) { return nullptr;
}
// We must give the generator a reference to an error to fill in. We don't // use it ourselves though because the only error we should get is for // implementation limits like 'stack frame too big' which we couldn't guard // against ahead of time. Returning nullptr is the right thing to do in // these cases.
UniqueChars error;
ModuleGenerator mg(*codeMeta_, compilerEnv_, compilerEnv_.initialState(),
nullptr, &error, nullptr); if (!mg.initializeCompleteTier(codeMetaForAsmJS_.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;
}
}
staticbool IsNumericNonFloatLiteral(ParseNode* pn) { // Note: '-' is never rolled into the number; numbers are always positive // and negations must be applied manually. return pn->isKind(ParseNodeKind::NumberExpr) ||
(pn->isKind(ParseNodeKind::NegExpr) &&
UnaryKid(pn)->isKind(ParseNodeKind::NumberExpr));
}
staticbool IsCallToGlobal(ModuleValidatorShared& m, ParseNode* pn, const ModuleValidatorShared::Global** global) { if (!pn->isKind(ParseNodeKind::CallExpr)) { returnfalse;
}
ParseNode* callee = CallCallee(pn); if (!callee->isKind(ParseNodeKind::Name)) { returnfalse;
}
// The JS grammar treats -42 as -(42) (i.e., with separate grammar // productions) for the unary - and literal 42). However, the asm.js spec // recognizes -42 (modulo parens, so -(42) and -((42))) as a single literal // so fold the two potential parse nodes into a single double value. staticdouble 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)) { // Float literals are explicitly coerced and thus the coerced literal may be // any valid (non-float) numeric literal.
MOZ_ASSERT(CallArgListLength(pn) == 1);
pn = CallArgList(pn); double d = ExtractNumericNonFloatValue(pn); return NumLit(NumLit::Float, DoubleValue(d));
}
double d = ExtractNumericNonFloatValue(pn, &pn);
// The asm.js spec syntactically distinguishes any literal containing a // decimal point or the literal -0 as having double type. if (NumberNodeHasFrac(pn) || IsNegativeZero(d)) { return NumLit(NumLit::Double, DoubleValue(d));
}
// The syntactic checks above rule out these double values.
MOZ_ASSERT(!IsNegativeZero(d));
MOZ_ASSERT(!std::isnan(d));
// Although doubles can only *precisely* represent 53-bit integers, they // can *imprecisely* represent integers much bigger than an int64_t. // Furthermore, d may be inf or -inf. In both cases, casting to an int64_t // is undefined, so test against the integer bounds using doubles. if (d < double(INT32_MIN) || d > double(UINT32_MAX)) { return NumLit(NumLit::OutOfRangeInt, UndefinedValue());
}
// With the above syntactic and range limitations, d is definitely an // integer in the range [INT32_MIN, UINT32_MAX] range.
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));
}
staticinlinebool IsLiteralInt(const NumLit& lit, uint32_t* u32) { switch (lit.which()) { case NumLit::Fixnum: case NumLit::BigUnsigned: case NumLit::NegativeInt:
*u32 = lit.toUint32(); returntrue; case NumLit::Double: case NumLit::Float: case NumLit::OutOfRangeInt: returnfalse;
}
MOZ_CRASH("Bad literal type");
}
[[nodiscard]] bool writeInt32Lit(int32_t i32) { return encoder().writeOp(Op::I32Const) && encoder().writeVarS32(i32);
}
[[nodiscard]] 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");
}
};
// Encapsulates the building of an asm bytecode function from an asm.js function // source code, packing the asm.js code into the asm bytecode form that can // be decoded and compiled with a FunctionCompiler. template <typename Unit> class MOZ_STACK_CLASS FunctionValidator : public FunctionValidatorShared { public:
FunctionValidator(ModuleValidator<Unit>& m, FunctionNode* fn)
: FunctionValidatorShared(m, fn, m.fc()) {}
/*****************************************************************************/ // asm.js type-checking and code-generation algorithm
staticbool CheckIdentifier(ModuleValidatorShared& m, ParseNode* usepn,
TaggedParserAtomIndex name) { if (name == TaggedParserAtomIndex::WellKnown::arguments() ||
name == TaggedParserAtomIndex::WellKnown::eval()) { return m.failName(usepn, "'%s' is not an allowed identifier", name);
} returntrue;
}
staticbool CheckModuleLevelName(ModuleValidatorShared& m, ParseNode* usepn,
TaggedParserAtomIndex name) { if (!CheckIdentifier(m, usepn, name)) { returnfalse;
}
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);
}
staticbool 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(stmt)) { return m.fail(stmt, "invalid asm.js statement");
}
}
returntrue;
}
staticbool CheckGlobalVariableInitConstant(ModuleValidatorShared& m,
TaggedParserAtomIndex 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");
}
staticbool 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);
} returntrue;
} case ParseNodeKind::PosExpr: {
*coerceTo = Type::Double; if (coercedExpr) {
*coercedExpr = UnaryKid(coercionNode);
} returntrue;
} case ParseNodeKind::CallExpr: { if (IsCoercionCall(m, coercionNode, coerceTo, coercedExpr)) { returntrue;
} break;
} default:;
}
return m.fail(coercionNode, "must be of the form +x, x|0 or fround(x)");
}
staticbool CheckGlobalVariableInitImport(ModuleValidatorShared& m,
TaggedParserAtomIndex varName,
ParseNode* initNode, bool isConst) {
Type coerceTo;
ParseNode* coercedExpr; if (!CheckTypeAnnotation(m, initNode, &coerceTo, &coercedExpr)) { returnfalse;
}
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);
TaggedParserAtomIndex field = DotMember(coercedExpr);
TaggedParserAtomIndex 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);
}
if (!IsUseOfName(bufArg, bufferName)) { return m.failName(bufArg, "argument to array view constructor must be '%s'",
bufferName);
}
returntrue;
}
staticbool CheckNewArrayView(ModuleValidatorShared& m,
TaggedParserAtomIndex varName,
ParseNode* newExpr) {
TaggedParserAtomIndex globalName = m.globalArgumentName(); if (!globalName) { return m.fail(
newExpr, "cannot create array view without an asm.js global parameter");
}
TaggedParserAtomIndex bufferName = m.bufferArgumentName(); if (!bufferName) { return m.fail(newExpr, "cannot create array view without an asm.js heap parameter");
}
ParseNode* ctorExpr = BinaryLeft(newExpr);
TaggedParserAtomIndex 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");
}
TaggedParserAtomIndex 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);
}
type = global->viewType();
}
if (!CheckNewArrayViewArgs(m, newExpr, bufferName)) { returnfalse;
}
return m.addArrayView(varName, type, field);
}
staticbool CheckGlobalMathImport(ModuleValidatorShared& m, ParseNode* initNode,
TaggedParserAtomIndex varName,
TaggedParserAtomIndex field) { // Math builtin, with the form glob.Math.[[builtin]]
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");
}
staticbool CheckGlobalDotImport(ModuleValidatorShared& m,
TaggedParserAtomIndex varName,
ParseNode* initNode) {
ParseNode* base = DotBase(initNode);
TaggedParserAtomIndex field = DotMember(initNode);
if (base->isKind(ParseNodeKind::DotExpr)) {
ParseNode* global = DotBase(base);
TaggedParserAtomIndex math = DotMember(base);
TaggedParserAtomIndex 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);
}
template <typename Unit> staticbool CheckModuleProcessingDirectives(ModuleValidator<Unit>& m) { auto& ts = m.parser().tokenStream; while (true) { bool matched; if (!ts.matchToken(&matched, TokenKind::String,
TokenStreamShared::SlashIsRegExp)) { returnfalse;
} if (!matched) { returntrue;
}
if (!IsIgnoredDirectiveName(ts.anyCharsAccess().currentToken().atom())) { return m.failCurrentOffset("unsupported processing directive");
}
TokenKind tt; if (!ts.getToken(&tt)) { returnfalse;
} if (tt != TokenKind::Semi) { return m.failCurrentOffset("expected semicolon after string literal");
}
}
}
template <typename Unit> staticbool CheckModuleGlobals(ModuleValidator<Unit>& m) { while (true) {
ParseNode* varStmt; if (!ParseVarOrConstStatement(m.parser(), &varStmt)) { returnfalse;
} if (!varStmt) { break;
} for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) { if (!CheckModuleGlobal(m, var,
varStmt->isKind(ParseNodeKind::ConstDecl))) { returnfalse;
}
}
}
returntrue;
}
staticbool ArgFail(FunctionValidatorShared& f, TaggedParserAtomIndex 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);
}
for (; stmt && stmt->isKind(ParseNodeKind::VarStmt);
stmt = NextNonEmptyStatement(stmt)) { for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) { if (!CheckVariable(f, var, &types, &inits)) { returnfalse;
}
}
}
MOZ_ASSERT(f.encoder().empty());
if (!EncodeLocalEntries(f.encoder(), types)) { returnfalse;
}
for (uint32_t i = 0; i < inits.length(); i++) {
NumLit lit = inits[i]; if (lit.isZeroBits()) { continue;
} if (!f.writeConstExpr(lit)) { returnfalse;
} if (!f.encoder().writeOp(Op::LocalSet)) { returnfalse;
} if (!f.encoder().writeVarU32(firstVar + i)) { returnfalse;
}
}
if (const FunctionValidatorShared::Local* local = f.lookupLocal(name)) { if (!f.encoder().writeOp(Op::LocalGet)) { returnfalse;
} if (!f.encoder().writeVarU32(local->slot)) { returnfalse;
}
*type = local->type; returntrue;
}
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::GlobalGet) &&
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);
}
template <typename Unit> staticbool 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->which() != ModuleValidatorShared::Global::ArrayView) { 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);
}
// Mask off the low bits to account for the clearing effect of a right shift // followed by the left shift implicit in the array access. E.g., H32[i>>2] // loses the low two bits.
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)) { returnfalse;
}
if (!pointerType.isIntish()) { return f.failf(pointerNode, "%s is not a subtype of int",
pointerType.toChars());
}
} else { // For legacy scalar access compatibility, accept Int8/Uint8 accesses // with no shift. 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)) { returnfalse;
} if (!pointerType.isInt()) { return f.failf(pointerNode, "%s is not a subtype of int",
pointerType.toChars());
}
}
// Don't generate the mask op if there is no need for it which could happen // for a shift of zero. if (mask != NoMask) { return f.writeInt32Lit(mask) && f.encoder().writeOp(Op::I32And);
}
returntrue;
}
staticbool WriteArrayAccessFlags(FunctionValidatorShared& f,
Scalar::Type viewType) { // asm.js only has naturally-aligned accesses.
size_t align = TypedArrayElemSize(viewType);
MOZ_ASSERT(std::has_single_bit(align)); if (!f.encoder().writeFixedU8(CeilingLog2(align))) { returnfalse;
}
// asm.js doesn't have constant offsets, so just encode a 0. return f.encoder().writeVarU32(0);
}
if (!CheckArrayAccess(f, ElemBase(elem), ElemIndex(elem), &viewType)) { returnfalse;
}
switch (viewType) { case Scalar::Int8: if (!f.encoder().writeOp(Op::I32Load8S)) returnfalse; break; case Scalar::Uint8: if (!f.encoder().writeOp(Op::I32Load8U)) returnfalse; break; case Scalar::Int16: if (!f.encoder().writeOp(Op::I32Load16S)) returnfalse; break; case Scalar::Uint16: if (!f.encoder().writeOp(Op::I32Load16U)) returnfalse; break; case Scalar::Uint32: case Scalar::Int32: if (!f.encoder().writeOp(Op::I32Load)) returnfalse; break; case Scalar::Float32: if (!f.encoder().writeOp(Op::F32Load)) returnfalse; break; case Scalar::Float64: if (!f.encoder().writeOp(Op::F64Load)) returnfalse; 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");
}
Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
}
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)) { returnfalse;
} break; case Scalar::Int16: case Scalar::Uint16: if (!f.encoder().writeOp(MozOp::I32TeeStore16)) { returnfalse;
} break; case Scalar::Int32: case Scalar::Uint32: if (!f.encoder().writeOp(MozOp::I32TeeStore)) { returnfalse;
} break; case Scalar::Float32: if (rhsType.isFloatish()) { if (!f.encoder().writeOp(MozOp::F32TeeStore)) { returnfalse;
}
} else { if (!f.encoder().writeOp(MozOp::F64TeeStoreF32)) { returnfalse;
}
} break; case Scalar::Float64: if (rhsType.isFloatish()) { if (!f.encoder().writeOp(MozOp::F32TeeStoreF64)) { returnfalse;
}
} else { if (!f.encoder().writeOp(MozOp::F64TeeStore)) { returnfalse;
}
} break; default:
MOZ_CRASH("unexpected scalar type");
}
if (!WriteArrayAccessFlags(f, viewType)) { returnfalse;
}
if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) {
Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
}
if (!f.encoder().writeOp(Op::LocalTee)) { returnfalse;
} if (!f.encoder().writeVarU32(lhsVar->slot)) { returnfalse;
}
if (!(rhsType <= lhsVar->type)) { return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(),
lhsVar->type.toChars());
}
*type = rhsType; returntrue;
}
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)) { returnfalse;
}
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)) { returnfalse;
} if (!f.encoder().writeVarU32(global->varOrConstIndex())) { returnfalse;
}
*type = rhsType; returntrue;
}
return f.failName(lhs, "'%s' not found in local or asm.js module scope",
name);
}
Type lhsType; if (!CheckExpr(f, lhs, &lhsType)) { returnfalse;
}
Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
}
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());
}
template <typename Unit> staticbool 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)) { returnfalse;
}
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> staticbool 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)) { returnfalse;
}
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> staticbool 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)) { returnfalse;
}
Op op = Op::Limit;
MozOp mozOp = MozOp::Limit; if (firstType.isMaybeDouble()) {
*type = Type::Double;
firstType = Type::MaybeDouble;
op = isMax ? Op::F64Max : Op::F64Min;
} elseif (firstType.isMaybeFloat()) {
*type = Type::Float;
firstType = Type::MaybeFloat;
op = isMax ? Op::F32Max : Op::F32Min;
} elseif (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)) { returnfalse;
} 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)) { returnfalse;
}
} else { if (!f.encoder().writeOp(mozOp)) { returnfalse;
}
}
}
returntrue;
}
using CheckArgType = bool (*)(FunctionValidatorShared& f, ParseNode* argNode,
Type type);
template <CheckArgType checkArg, typename Unit> staticbool 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)) { returnfalse;
}
if (!checkArg(f, argNode, type)) { returnfalse;
}
if (!args->append(Type::canonicalize(type).canonicalToValType())) { returnfalse;
}
} if (args->length() > MaxParams) { return f.fail(callNode, "too many parameters");
} returntrue;
}
staticbool CheckSignatureAgainstExisting(ModuleValidatorShared& m,
ParseNode* usepn, const FuncType& sig, const FuncType& existing) { if (!FuncType::strictlyEquals(sig, existing)) { return m.failf(usepn, "incompatible argument types to function");
} returntrue;
}
if (!CheckSignatureAgainstExisting(m, usepn, sig, existingSig)) { returnfalse;
}
*func = existing; returntrue;
}
staticbool 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());
} returntrue;
}
if (!f.writeCall(callNode, MozOp::OldCallDirect)) { returnfalse;
}
if (!f.encoder().writeVarU32(callee->funcDefIndex())) { returnfalse;
}
*type = Type::ret(ret); returntrue;
}
template <typename Unit> staticbool CheckFuncPtrTableAgainstExisting(ModuleValidator<Unit>& m,
ParseNode* usepn,
TaggedParserAtomIndex 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.codeMeta()->types->type(table.sigIndex()).funcType())) { returnfalse;
}
if (!tableNode->isKind(ParseNodeKind::Name)) { return f.fail(tableNode, "expecting name of function-pointer array");
}
TaggedParserAtomIndex 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");
}
uint32_t mask; if (!IsLiteralInt(f.m(), maskNode, &mask) || mask == UINT32_MAX ||
!std::has_single_bit(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)) { returnfalse;
}
if (!indexType.isIntish()) { return f.failf(indexNode, "%s is not a subtype of intish",
indexType.toChars());
}
ValTypeVector args; if (!CheckCallArgs<CheckIsArgType>(f, callNode, &args)) { returnfalse;
}
if (!f.writeCall(callNode, MozOp::OldCallIndirect)) { returnfalse;
}
// Call signature if (!f.encoder().writeVarU32(f.m().table(tableIndex).sigIndex())) { returnfalse;
}
*type = Type::ret(ret); returntrue;
}
staticbool CheckIsExternType(FunctionValidatorShared& f, ParseNode* argNode,
Type type) { if (!type.isExtern()) { return f.failf(argNode, "%s is not a subtype of extern", type.toChars());
} returntrue;
}
Type firstType;
ParseNode* argNode = CallArgList(callNode); if (!CheckExpr(f, argNode, &firstType)) { returnfalse;
}
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)) { returnfalse;
}
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)) { returnfalse;
}
} else { if (!f.encoder().writeOp(mozf64)) { returnfalse;
}
}
} else { if (!f.encoder().writeOp(f32)) { returnfalse;
}
}
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())");
}
staticbool CoerceResult(FunctionValidatorShared& f, ParseNode* expr,
Type expected, Type actual, Type* type) {
MOZ_ASSERT(expected.isCanonical());
// At this point, the bytecode resembles this: // | the thing we wanted to coerce | current position |> switch (expected.which()) { case Type::Void: if (!actual.isVoid()) { if (!f.encoder().writeOp(Op::Drop)) { returnfalse;
}
} 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)) { returnfalse;
} break; case Type::Double: if (actual.isMaybeDouble()) { // No conversion necessary.
} elseif (actual.isMaybeFloat()) { if (!f.encoder().writeOp(Op::F64PromoteF32)) { returnfalse;
}
} elseif (actual.isSigned()) { if (!f.encoder().writeOp(Op::F64ConvertI32S)) { returnfalse;
}
} elseif (actual.isUnsigned()) { if (!f.encoder().writeOp(Op::F64ConvertI32U)) { returnfalse;
}
} 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");
}
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;
}
}
// The block depth isn't taken into account here, because a comma list can't // contain breaks and continues and nested control flow structures. if (!f.encoder().writeOp(Op::Block)) { returnfalse;
}
size_t typeAt; if (!f.encoder().writePatchableFixedU7(&typeAt)) { returnfalse;
}
ParseNode* pn = operands; for (; NextNode(pn); pn = NextNode(pn)) { if (!CheckAsExprStatement(f, pn)) { returnfalse;
}
}
template <typename Unit> staticbool IsValidIntMultiplyConstant(ModuleValidator<Unit>& m,
ParseNode* expr) { if (!IsNumericLiteral(m, expr)) { returnfalse;
}
NumLit lit = ExtractNumericLiteral(m, expr); switch (lit.which()) { case NumLit::Fixnum: case NumLit::NegativeInt: if (Abs(lit.toInt32()) < (uint32_t(1) << 20)) { returntrue;
} returnfalse; case NumLit::BigUnsigned: case NumLit::Double: case NumLit::Float: case NumLit::OutOfRangeInt: returnfalse;
}
Type lhsType; if (!CheckExpr(f, lhs, &lhsType)) { returnfalse;
}
Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
}
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);
}
return f.failf(
expr, "arguments to / or %% must both be double?, float?, signed, or unsigned; " "%s and %s are given",
lhsType.toChars(), rhsType.toChars());
}
Type lhsType, rhsType; if (!CheckExpr(f, lhs, &lhsType)) { returnfalse;
} if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
}
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");
}
} elseif (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");
}
} elseif (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");
}
} elseif (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");
}
uint32_t i; if (!onlyOnRight && IsLiteralInt(f.m(), lhs, &i) &&
i == uint32_t(identityElement)) {
Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
} if (!rhsType.isIntish()) { return f.failf(bitwise, "%s is not a subtype of intish",
rhsType.toChars());
} returntrue;
}
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)) { returnfalse;
} if (!lhsType.isIntish()) { return f.failf(bitwise, "%s is not a subtype of intish",
lhsType.toChars());
} returntrue;
}
Type lhsType; if (!CheckExpr(f, lhs, &lhsType)) { returnfalse;
}
Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { returnfalse;
}
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)) returnfalse; break; case ParseNodeKind::BitAndExpr: if (!f.encoder().writeOp(Op::I32And)) returnfalse; break; case ParseNodeKind::BitXorExpr: if (!f.encoder().writeOp(Op::I32Xor)) returnfalse; break; case ParseNodeKind::LshExpr: if (!f.encoder().writeOp(Op::I32Shl)) returnfalse; break; case ParseNodeKind::RshExpr: if (!f.encoder().writeOp(Op::I32ShrS)) returnfalse; break; case ParseNodeKind::UrshExpr: if (!f.encoder().writeOp(Op::I32ShrU)) returnfalse; break; default:
MOZ_CRASH("not a bitwise op");
}
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);
Type condType; if (!CheckExpr(f, cond, &condType)) { returnfalse;
} if (!condType.isInt()) { return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
if (!f.encoder().writeOp(Op::I32Eqz)) { returnfalse;
}
// brIf (i32.eqz $f) $out return f.writeBreakIf();
}
// A for-loop `for (#init; #cond; #inc) #body` is equivalent to: // (block // depth X // (#init) // (block $after_loop // depth X+1 (block) // (loop $loop_top // depth X+2 (loop) // (brIf $after (eq 0 #cond)) // (block $after_body #body) // depth X+3 // #inc // (br $loop_top) // ) // ) // ) // A break in the body should break out to $after_loop, i.e. depth + 1. // A continue in the body should break out to $after_body, i.e. depth + 3. if (labels && !f.addLabels(*labels, 1, 3)) { returnfalse;
}
if (!f.pushUnbreakableBlock()) { returnfalse;
}
if (maybeInit && !CheckAsExprStatement(f, maybeInit)) { returnfalse;
}
{ if (!f.pushLoop()) { returnfalse;
}
if (maybeCond && !CheckLoopConditionOnEntry(f, maybeCond)) { returnfalse;
}
{ // Continuing in the body should just break out to the increment. if (!f.pushContinuableBlock()) { returnfalse;
} if (!CheckStatement(f, body)) { returnfalse;
} if (!f.popContinuableBlock()) { returnfalse;
}
}
if (maybeInc && !CheckAsExprStatement(f, maybeInc)) { returnfalse;
}
if (!f.writeContinue()) { returnfalse;
} if (!f.popLoop()) { returnfalse;
}
}
// A do-while loop `do { #body } while (#cond)` is equivalent to: // (block $after_loop // depth X // (loop $top // depth X+1 // (block #body) // depth X+2 // (brIf #cond $top) // ) // ) // A break should break out of the entire loop, i.e. at depth 0. // A continue should break out to the condition, i.e. at depth 2. if (labels && !f.addLabels(*labels, 0, 2)) { returnfalse;
}
if (!f.pushLoop()) { returnfalse;
}
{ // An unlabeled continue in the body should break out to the condition. if (!f.pushContinuableBlock()) { returnfalse;
} if (!CheckStatement(f, body)) { returnfalse;
} if (!f.popContinuableBlock()) { returnfalse;
}
}
Type condType; if (!CheckExpr(f, cond, &condType)) { returnfalse;
} if (!condType.isInt()) { return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
if (!f.writeContinueIf()) { returnfalse;
}
if (!f.popLoop()) { returnfalse;
} if (labels) {
f.removeLabels(*labels);
} returntrue;
}
Type condType; if (!CheckExpr(f, cond, &condType)) { returnfalse;
} if (!condType.isInt()) { return f.failf(cond, "%s is not a subtype of int", condType.toChars());
}
size_t typeAt; if (!f.pushIf(&typeAt)) { returnfalse;
}
f.setIfType(typeAt, TypeCode::BlockVoid);
if (!CheckStatement(f, thenStmt)) { returnfalse;
}
if (elseStmt) { if (!f.switchToElse()) { returnfalse;
}
if (elseStmt->isKind(ParseNodeKind::IfStmt)) {
ifStmt = elseStmt; if (numIfEnd++ == UINT32_MAX) { returnfalse;
} goto recurse;
}
if (!CheckStatement(f, elseStmt)) { returnfalse;
}
}
for (uint32_t i = 0; i != numIfEnd; ++i) { if (!f.popIf()) { returnfalse;
}
}
returntrue;
}
staticbool 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");
}
returntrue;
}
staticbool 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");
}
}
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); returntrue;
}
template <typename Unit> staticbool CheckSwitchExpr(FunctionValidator<Unit>& f, ParseNode* switchExpr) {
Type exprType; if (!CheckExpr(f, switchExpr, &exprType)) { returnfalse;
} if (!exprType.isSigned()) { return f.failf(switchExpr, "%s is not a subtype of signed",
exprType.toChars());
} returntrue;
}
// A switch will be constructed as: // - the default block wrapping all the other blocks, to be able to break // out of the switch with an unlabeled break statement. It has two statements // (an inner block and the default expr). asm.js rules require default to be at // the end, so the default block always encloses all the cases blocks. // - one block per case between low and high; undefined cases just jump to the // default case. Each of these blocks contain two statements: the next case's // block and the possibly empty statement list comprising the case body. The // last block pushed is the first case so the (relative) branch target therefore // matches the sequential order of cases. // - one block for the br_table, so that the first break goes to the first // case's block. template <typename Unit> staticbool CheckSwitch(FunctionValidator<Unit>& f, ParseNode* switchStmt) {
MOZ_ASSERT(switchStmt->isKind(ParseNodeKind::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)) { returnfalse;
} return f.encoder().writeOp(Op::Drop);
}
if (!CheckDefaultAtEnd(f, stmt)) { returnfalse;
}
int32_t low = 0, high = 0;
uint32_t tableLength = 0; if (!CheckSwitchRange(f, stmt, &low, &high, &tableLength)) { returnfalse;
}
// Open the wrapping breakable default block. if (!f.pushBreakableBlock()) { returnfalse;
}
// Open all the case blocks. for (uint32_t i = 0; i < numCases; i++) { if (!f.pushUnbreakableBlock()) { returnfalse;
}
}
// Open the br_table block. if (!f.pushUnbreakableBlock()) { returnfalse;
}
// The default block is the last one.
uint32_t defaultDepth = numCases;
// Subtract lowest case value, so that all the cases start from 0. if (low) { if (!CheckSwitchExpr(f, switchExpr)) { returnfalse;
} if (!f.writeInt32Lit(low)) { returnfalse;
} if (!f.encoder().writeOp(Op::I32Sub)) { returnfalse;
}
} else { if (!CheckSwitchExpr(f, switchExpr)) { returnfalse;
}
}
// Start the br_table block. if (!f.encoder().writeOp(Op::BrTable)) { returnfalse;
}
// Write the number of cases (tableLength - 1 + 1 (default)). // Write the number of cases (tableLength - 1 + 1 (default)). if (!f.encoder().writeVarU32(tableLength)) { returnfalse;
}
// Each case value describes the relative depth to the actual block. When // a case is not explicitly defined, it goes to the default. for (size_t i = 0; i < tableLength; i++) {
uint32_t target =
caseDepths[i] == CASE_NOT_DEFINED ? defaultDepth : caseDepths[i]; if (!f.encoder().writeVarU32(target)) { returnfalse;
}
}
// Write the default depth. if (!f.encoder().writeVarU32(defaultDepth)) { returnfalse;
}
// Our br_table is done. Close its block, write the cases down in order. if (!f.popUnbreakableBlock()) { returnfalse;
}
for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) { if (!CheckStatement(f, CaseBody(stmt))) { returnfalse;
} if (!f.popUnbreakableBlock()) { returnfalse;
}
}
// Write the default block. if (stmt && IsDefaultCase(stmt)) { if (!CheckStatement(f, CaseBody(stmt))) { returnfalse;
}
}
// Close the wrapping block. return f.popBreakableBlock();
}
staticbool CheckReturnType(FunctionValidatorShared& f, ParseNode* usepn,
Type ret) {
Maybe<ValType> type = ret.canonicalToReturnType();
if (!f.hasAlreadyReturned()) {
f.setReturnedType(type); returntrue;
}
if (f.returnedType() != type) { return f.failf(usepn, "%s incompatible with previous return of type %s",
ToString(type, nullptr).get(),
ToString(f.returnedType(), nullptr).get());
}
TokenKind tk; if (!tokenStream.getToken(&tk, TokenStreamShared::SlashIsRegExp)) { returnfalse;
} if (tk == TokenKind::Mul) { return m.failCurrentOffset("unexpected generator function");
} if (!TokenKindIsPossibleIdentifier(tk)) { returnfalse; // The regular parser will throw a SyntaxError, no need to // m.fail.
}
TaggedParserAtomIndex name = m.parser().bindingIdentifier(YieldIsName); if (!name) { returnfalse;
}
template <typename Unit> staticbool CheckFunction(ModuleValidator<Unit>& m) { // asm.js modules can be quite large when represented as parse trees so pop // the backing LifoAlloc after parsing/compiling each function. Release the // parser's lifo memory after the last use of a parse node.
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)) { returnfalse;
}
if (!CheckFunctionHead(m, funNode)) { returnfalse;
}
staticbool 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());
}
}
returntrue;
}
template <typename Unit> staticbool CheckFunctions(ModuleValidator<Unit>& m) { while (true) {
TokenKind tk; if (!PeekToken(m.parser(), &tk)) { returnfalse;
}
if (tk != TokenKind::Function) { break;
}
if (!CheckFunction(m)) { returnfalse;
}
}
return CheckAllFunctionsDefined(m);
}
template <typename Unit> staticbool 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 (!std::has_single_bit(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");
}
TaggedParserAtomIndex 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.codeMeta()->types->type(func->sigIndex()).funcType(); if (sig) { if (!FuncType::strictlyEquals(*sig, funcSig)) { return m.fail(elem, "all functions in table must have same signature");
}
} else {
sig = &funcSig;
}
if (!elemFuncDefIndices.append(func->funcDefIndex())) { returnfalse;
}
}
FuncType copy; if (!copy.clone(*sig)) { returnfalse;
}
uint32_t tableIndex; if (!CheckFuncPtrTableAgainstExisting(m, var, var->as<NameNode>().name(),
std::move(copy), mask, &tableIndex)) { returnfalse;
}
if (!m.defineFuncPtrTable(tableIndex, std::move(elemFuncDefIndices))) { return m.fail(var, "duplicate function-pointer definition");
}
returntrue;
}
template <typename Unit> staticbool CheckFuncPtrTables(ModuleValidator<Unit>& m) { while (true) {
ParseNode* varStmt; if (!ParseVarOrConstStatement(m.parser(), &varStmt)) { returnfalse;
} if (!varStmt) { break;
} for (ParseNode* var = VarListHead(varStmt); var; var = NextNode(var)) { if (!CheckFuncPtrTable(m, var)) { returnfalse;
}
}
}
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());
}
}
returntrue;
}
staticbool CheckModuleExportFunction(
ModuleValidatorShared& m, ParseNode* pn,
TaggedParserAtomIndex maybeFieldName = TaggedParserAtomIndex::null()) { if (!pn->isKind(ParseNodeKind::Name)) { return m.fail(pn, "expected name of exported function");
}
TaggedParserAtomIndex 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);
}
staticbool 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");
}
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)) { returnfalse;
}
}
ScriptedCaller scriptedCaller; if (parser.ss->filename()) {
scriptedCaller.line = 0; // unused
scriptedCaller.source = DuplicateString(parser.ss->filename()); if (!scriptedCaller.source) { return nullptr;
}
}
// The default options are fine for asm.js
SharedCompileArgs args =
CompileArgs::buildForAsmJS(std::move(scriptedCaller)); if (!args) {
ReportOutOfMemory(fc); return nullptr;
}
staticbool HasPureCoercion(JSContext* cx, HandleValue v) { // Ideally, we'd reject all non-primitives, but Emscripten has a bug that // generates code that passes functions for some imports. To avoid breaking // all the code that contains this bug, we make an exception for functions // that don't have user-defined valueOf or toString, for their coercions // are not observable and coercion via ToNumber/ToInt32 definitely produces // NaN/0. We should remove this special case later once most apps have been // built with newer Emscripten. return v.toObject().is<JSFunction>() &&
HasNoToPrimitiveMethodPure(&v.toObject(), cx) &&
HasObjectValueOfMethodPure(&v.toObject(), cx) &&
HasNativeMethodPure(&v.toObject(), cx->names().toString, fun_toString,
cx);
}
static InlinableNative ToInlinableNative(AsmJSMathBuiltinFunction func) { switch (func) { case AsmJSMathBuiltin_sin: return InlinableNative::MathSin; case AsmJSMathBuiltin_cos: return InlinableNative::MathCos; case AsmJSMathBuiltin_tan: return InlinableNative::MathTan; case AsmJSMathBuiltin_asin: return InlinableNative::MathASin; case AsmJSMathBuiltin_acos: return InlinableNative::MathACos; case AsmJSMathBuiltin_atan: return InlinableNative::MathATan; case AsmJSMathBuiltin_ceil: return InlinableNative::MathCeil; case AsmJSMathBuiltin_floor: return InlinableNative::MathFloor; case AsmJSMathBuiltin_exp: return InlinableNative::MathExp; case AsmJSMathBuiltin_log: return InlinableNative::MathLog; case AsmJSMathBuiltin_pow: return InlinableNative::MathPow; case AsmJSMathBuiltin_sqrt: return InlinableNative::MathSqrt; case AsmJSMathBuiltin_abs: return InlinableNative::MathAbs; case AsmJSMathBuiltin_atan2: return InlinableNative::MathATan2; case AsmJSMathBuiltin_imul: return InlinableNative::MathImul; case AsmJSMathBuiltin_fround: return InlinableNative::MathFRound; case AsmJSMathBuiltin_min: return InlinableNative::MathMin; case AsmJSMathBuiltin_max: return InlinableNative::MathMax; case AsmJSMathBuiltin_clz32: return InlinableNative::MathClz32;
}
MOZ_CRASH("Invalid asm.js math builtin function");
}
if (global.constantKind() == AsmJSGlobal::MathConstant) { if (!GetDataProperty(cx, v, cx->names().Math, &v)) { returnfalse;
}
}
if (!GetDataProperty(cx, v, global.field(), &v)) { returnfalse;
}
if (!v.isNumber()) { return LinkFail(cx, "math / global constant value needs to be a number");
}
// NaN != NaN if (std::isnan(global.constantValue())) { if (!std::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");
}
}
returntrue;
}
staticbool CheckBuffer(JSContext* cx, const CodeMetadata& codeMeta,
HandleValue bufferVal,
MutableHandle<ArrayBufferObject*> buffer) { if (!bufferVal.isObject()) { return LinkFail(cx, "buffer must be an object");
}
JSObject* bufferObj = &bufferVal.toObject();
if (codeMeta.memories[0].isShared()) { if (!bufferObj->is<SharedArrayBufferObject>()) { return LinkFail(
cx, "shared views can only be constructed onto SharedArrayBuffer");
} return LinkFail(cx, "Unable to prepare SharedArrayBuffer for asm.js use");
}
if (!bufferObj->is<ArrayBufferObject>()) { return LinkFail(cx, "unshared views can only be constructed onto ArrayBuffer");
}
buffer.set(&bufferObj->as<ArrayBufferObject>());
size_t memoryLength = buffer->byteLength();
if (!IsValidAsmJSHeapLength(memoryLength)) {
UniqueChars msg; if (memoryLength > MaxHeapLength) {
msg = JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64 " is not a valid heap length - it is too long." " The longest valid length is 0x%" PRIx64,
uint64_t(memoryLength), MaxHeapLength);
} else {
msg = JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64 " is not a valid heap length. The next " "valid length is 0x%" PRIx64,
uint64_t(memoryLength),
RoundUpToNextValidAsmJSHeapLength(memoryLength));
} if (!msg) { returnfalse;
} return LinkFail(cx, msg.get());
}
// This check is sufficient without considering the size of the loaded datum // because heap loads and stores start on an aligned boundary and the heap // byteLength has larger alignment.
uint64_t minMemoryLength = codeMeta.memories.length() != 0
? codeMeta.memories[0].initialLength()
: 0;
MOZ_ASSERT((minMemoryLength - 1) <= INT32_MAX); if (memoryLength < minMemoryLength) {
UniqueChars msg(JS_smprintf("ArrayBuffer byteLength of 0x%" PRIx64 " is less than 0x%" PRIx64 " (the " "size implied " "by const heap accesses).",
uint64_t(memoryLength), minMemoryLength)); if (!msg) { returnfalse;
} return LinkFail(cx, msg.get());
}
// ArrayBuffer lengths in SpiderMonkey used to be restricted to <= INT32_MAX, // but that has since been relaxed for the benefit of wasm. We keep the old // limit for asm.js so as to avoid having to worry about whether the asm.js // implementation is safe for larger heaps. if (memoryLength >= INT32_MAX) {
UniqueChars msg(
JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64 " is too large for asm.js (implementation limit).",
uint64_t(memoryLength))); if (!msg) { returnfalse;
} return LinkFail(cx, msg.get());
}
if (buffer->isResizable()) { return LinkFail(cx, "Unable to prepare resizable ArrayBuffer for asm.js use");
}
if (buffer->isImmutable()) { return LinkFail(cx, "Unable to prepare immutable ArrayBuffer for asm.js use");
}
if (!buffer->prepareForAsmJS()) { return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
}
// Source discarding is allowed to affect JS semantics because it is never // enabled for normal JS content. bool haveSource; if (!ScriptSource::loadSource(cx, source, &haveSource)) { returnfalse;
} if (!haveSource) {
JS_ReportErrorASCII(cx, "asm.js link failure with source discarding enabled"); returnfalse;
}
uint32_t begin = codeMetaForAsmJS.toStringStart;
uint32_t end = codeMetaForAsmJS.srcEndAfterCurly();
Rooted<JSLinearString*> src(cx, source->substringDontDeflate(cx, begin, end)); if (!src) { returnfalse;
}
// The exported function inherits an implicit strict context if the module // also inherited it somehow. if (codeMetaForAsmJS.strict) {
options.setForceStrictMode();
}
AutoStableStringChars linearChars(cx); if (!linearChars.initTwoByte(cx, src)) { returnfalse;
}
SourceText<char16_t> srcBuf; if (!srcBuf.initMaybeBorrowed(cx, linearChars)) { returnfalse;
}
// Call the function we just recompiled.
args.setCallee(ObjectValue(*fun)); return InternalCallOrConstruct(
cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT);
}
// Implements the semantics of an asm.js module function that has been // successfully validated. bool js::InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
Rooted<WasmInstanceObject*> instanceObj(cx);
RootedObject exportObj(cx); if (!TryInstantiate(cx, args, module, codeMetaForAsmJS, &instanceObj,
&exportObj)) { // Link-time validation checks failed, so reparse the entire asm.js // module from scratch to get normal interpreted bytecode which we can // simply Invoke. Very slow. return HandleInstantiationFailure(cx, args, codeMetaForAsmJS);
}
// Per the asm.js standard convention, whether failure sets a pending // exception determines whether to attempt non-asm.js reparsing, so ignore // the return value below.
(void)parser.warningNoOffset(JSMSG_USE_ASM_TYPE_FAIL, str ? str : ""); returnfalse;
}
// asm.js requires Ion to be available on the current hardware/OS and to be // enabled for wasm, since asm.js compilation goes via wasm. staticbool IsAsmJSCompilerAvailable(JSContext* cx) { return HasPlatformSupport() && WasmCompilerForAsmJSAvailable(cx);
}
staticbool EstablishPreconditions(frontend::ParserBase& parser) { switch (parser.options().asmJSOption()) { case AsmJSOption::DisabledByAsmJSPref: return TypeFailureWarning(
parser, "Asm.js optimizer disabled by 'asmjs' runtime option"); case AsmJSOption::DisabledByLinker: return TypeFailureWarning(
parser, "Asm.js optimizer disabled by linker (instantiation failure)"); case AsmJSOption::DisabledByNoWasmCompiler: return TypeFailureWarning(parser, "Asm.js optimizer disabled because no suitable " "wasm compiler is available"); case AsmJSOption::DisabledByDebugger: return TypeFailureWarning(
parser, "Asm.js optimizer disabled because debugger is active"); case AsmJSOption::Enabled: break;
}
if (parser.pc_->isGenerator()) { return TypeFailureWarning(parser, "Asm.js optimizer disabled in generator context");
}
if (parser.pc_->isAsync()) { return TypeFailureWarning(parser, "Asm.js optimizer disabled in async context");
}
if (parser.pc_->isArrowFunction()) { return TypeFailureWarning(
parser, "Asm.js optimizer disabled in arrow function context");
}
// Class constructors are also methods if (parser.pc_->isMethod() || parser.pc_->isGetterOrSetter()) { return TypeFailureWarning(
parser, "Asm.js optimizer disabled in class constructor or method context");
}
// Various conditions disable asm.js optimizations. if (!EstablishPreconditions(parser)) { return NoExceptionPending(fc);
}
// "Checking" parses, validates and compiles, producing a fully compiled // WasmModuleObject as result. unsigned time;
SharedModule module = CheckModule(fc, parserAtoms, parser, stmtList, &time); if (!module) { return NoExceptionPending(fc);
}
// Finished! Save the ref-counted module on the FunctionBox. When JSFunctions // are eventually allocated we will create an asm.js constructor for it.
FunctionBox* funbox = parser.pc_->functionBox();
MOZ_ASSERT(funbox->isInterpreted()); if (!funbox->setAsmJSModule(module)) { return NoExceptionPending(fc);
}
// Success! Write to the console with a "warning" message indicating // total compilation time.
*validated = true;
SuccessfulValidation(parser, time); return NoExceptionPending(fc);
}
// The heap length is limited by what a wasm memory32 can handle. if (length > MaxMemoryBytes(AddressType::I32, wasm::PageSize::Standard)) { returnfalse;
}
// asm.js specifies that the heap size must fit in an ARM immediate. return IsValidARMImmediate(length);
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen1.568Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.