/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * * Copyright 2014 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "wasm/AsmJS.h" #include "mozilla/Attributes.h" #include "mozilla/Compression.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Maybe.h" #include "mozilla/ScopeExit.h" #include "mozilla/Sprintf.h" // SprintfLiteral #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "mozilla/Variant.h" #include #include #include "jsmath.h" #include "frontend/BytecodeCompiler.h" // CompileStandaloneFunction #include "frontend/FrontendContext.h" // js::FrontendContext #include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind #include "frontend/ParseNode.h" #include "frontend/Parser.h" #include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex #include "frontend/SharedContext.h" // TopLevelFunction #include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher #include "gc/GC.h" #include "gc/Policy.h" #include "jit/InlinableNatives.h" #include "js/BuildId.h" // JS::BuildIdCharVector #include "js/experimental/JitInfo.h" #include "js/friend/ErrorMessages.h" // JSMSG_* #include "js/MemoryMetrics.h" #include "js/Printf.h" #include "js/ScalarType.h" // js::Scalar::Type #include "js/SourceText.h" #include "js/StableStringChars.h" #include "js/Wrapper.h" #include "util/DifferentialTesting.h" #include "util/StringBuffer.h" #include "util/Text.h" #include "vm/ErrorReporting.h" #include "vm/FunctionFlags.h" // js::FunctionFlags #include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind #include "vm/Interpreter.h" #include "vm/SelfHosting.h" #include "vm/Time.h" #include "vm/TypedArrayObject.h" #include "vm/Warnings.h" // js::WarnNumberASCII #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" #include "wasm/WasmInstance-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::AsVariant; using mozilla::CeilingLog2; using mozilla::HashGeneric; using mozilla::IsNegativeZero; using mozilla::IsPositiveZero; using mozilla::IsPowerOfTwo; using mozilla::Nothing; using mozilla::PodZero; using mozilla::PositiveInfinity; using mozilla::Some; using mozilla::Utf8Unit; using mozilla::Compression::LZ4; using FunctionVector = JS::GCVector; /*****************************************************************************/ // A wasm module can either use no memory, a unshared memory (ArrayBuffer) or // shared memory (SharedArrayBuffer). enum class MemoryUsage { None = false, Unshared = 1, Shared = 2 }; // The asm.js valid heap lengths are precisely the WASM valid heap lengths for // ARM greater or equal to MinHeapLength static const size_t MinHeapLength = PageSize; // 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(). static const uint64_t MaxHeapLength = 0x7f000000; static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) { if (length <= MinHeapLength) { return MinHeapLength; } return wasm::RoundUpToNextValidARMImmediate(length); } static uint64_t DivideRoundingUp(uint64_t a, uint64_t b) { return (a + (b - 1)) / b; } /*****************************************************************************/ // asm.js module object // The asm.js spec recognizes this set of builtin Math functions. 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 }; // LitValPOD is a restricted version of LitVal suitable for asm.js that is // always POD. 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 (valType_.typeCode()) { 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_v, "must be POD to be simply serialized/deserialized"); // An AsmJSGlobal represents a JS global variable in the asm.js module function. 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 friend class ModuleValidator; public: AsmJSGlobal() = default; AsmJSGlobal(Which which, UniqueChars field) { mozilla::PodZero(&pod); // zero padding for Valgrind 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_; } // 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; // 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_; public: AsmJSImport() = default; explicit AsmJSImport(uint32_t ffiIndex) : ffiIndex_(ffiIndex) {} uint32_t ffiIndex() const { return ffiIndex_; } }; using AsmJSImportVector = Vector; // 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. 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_; } }; using AsmJSExportVector = Vector; // Holds the immutable guts of an AsmJSModule. // // AsmJSMetadata is built incrementally by ModuleValidator and then shared // immutably between AsmJSModules. 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; // 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 shouldResistFingerprinting = false; RefPtr source; 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 = default; 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"); } bool mutedErrors() const override { return source->mutedErrors(); } const char16_t* displayURL() const override { return source->hasDisplayURL() ? source->displayURL() : nullptr; } ScriptSource* maybeScriptSource() const override { return source.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; } }; using MutableAsmJSMetadata = RefPtr; /*****************************************************************************/ // ParseNode utilities static inline ParseNode* NextNode(ParseNode* pn) { return pn->pn_next; } static inline ParseNode* UnaryKid(ParseNode* pn) { return pn->as().kid(); } static inline ParseNode* BinaryRight(ParseNode* pn) { return pn->as().right(); } static inline ParseNode* BinaryLeft(ParseNode* pn) { return pn->as().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().kid1(); } static inline ParseNode* TernaryKid2(ParseNode* pn) { return pn->as().kid2(); } static inline ParseNode* TernaryKid3(ParseNode* pn) { return pn->as().kid3(); } static inline ParseNode* ListHead(ParseNode* pn) { return pn->as().head(); } static inline unsigned ListLength(ParseNode* pn) { return pn->as().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().isDefault(); } static inline ParseNode* CaseExpr(ParseNode* pn) { return pn->as().caseExpression(); } static inline ParseNode* CaseBody(ParseNode* pn) { return pn->as().statementList(); } static inline ParseNode* BinaryOpLeft(ParseNode* pn) { MOZ_ASSERT(pn->isBinaryOperation()); MOZ_ASSERT(pn->as().count() == 2); return ListHead(pn); } static inline ParseNode* BinaryOpRight(ParseNode* pn) { MOZ_ASSERT(pn->isBinaryOperation()); MOZ_ASSERT(pn->as().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 TaggedParserAtomIndex LoopControlMaybeLabel(ParseNode* pn) { MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) || pn->isKind(ParseNodeKind::ContinueStmt)); return pn->as().label(); } static inline TaggedParserAtomIndex LabeledStatementLabel(ParseNode* pn) { return pn->as().label(); } static inline ParseNode* LabeledStatementStatement(ParseNode* pn) { return pn->as().statement(); } static double NumberNodeValue(ParseNode* pn) { return pn->as().value(); } static bool NumberNodeHasFrac(ParseNode* pn) { return pn->as().decimalPoint() == HasDecimal; } static ParseNode* DotBase(ParseNode* pn) { return &pn->as().expression(); } static TaggedParserAtomIndex DotMember(ParseNode* pn) { return pn->as().name(); } static ParseNode* ElemBase(ParseNode* pn) { return &pn->as().expression(); } static ParseNode* ElemIndex(ParseNode* pn) { return &pn->as().key(); } static inline TaggedParserAtomIndex FunctionName(FunctionNode* funNode) { if (auto name = funNode->funbox()->explicitName()) { return name; } return TaggedParserAtomIndex::null(); } static inline ParseNode* FunctionFormalParametersList(FunctionNode* fn, unsigned* numFormals) { ParamsBodyNode* argsBody = fn->body(); // 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()) { MOZ_ASSERT(argsBody->last()->as().scopeBody()->isKind( ParseNodeKind::StatementList)); (*numFormals)--; } return argsBody->head(); } static inline 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; } static inline bool IsNormalObjectField(ParseNode* pn) { return pn->isKind(ParseNodeKind::PropertyDefinition) && pn->as().accessorType() == AccessorType::None && BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName); } static inline TaggedParserAtomIndex ObjectNormalFieldName(ParseNode* pn) { MOZ_ASSERT(IsNormalObjectField(pn)); MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName)); return BinaryLeft(pn)->as().atom(); } static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) { MOZ_ASSERT(IsNormalObjectField(pn)); return BinaryRight(pn); } static inline bool IsUseOfName(ParseNode* pn, TaggedParserAtomIndex name) { return pn->isName(name); } static inline bool IsIgnoredDirectiveName(TaggedParserAtomIndex atom) { return atom != TaggedParserAtomIndex::WellKnown::useStrict(); } static inline bool IsIgnoredDirective(ParseNode* pn) { return pn->isKind(ParseNodeKind::ExpressionStmt) && UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) && IsIgnoredDirectiveName(UnaryKid(pn)->as().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 static bool GetToken(AsmJSParser& parser, TokenKind* tkp) { auto& ts = parser.tokenStream; TokenKind tk; while (true) { if (!ts.getToken(&tk, TokenStreamShared::SlashIsRegExp)) { return false; } if (tk != TokenKind::Semi) { break; } } *tkp = tk; return true; } template static bool PeekToken(AsmJSParser& parser, TokenKind* tkp) { auto& ts = parser.tokenStream; TokenKind tk; while (true) { if (!ts.peekToken(&tk, TokenStream::SlashIsRegExp)) { return false; } if (tk != TokenKind::Semi) { break; } ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::SlashIsRegExp); } *tkp = tk; return true; } template static bool ParseVarOrConstStatement(AsmJSParser& 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; } /*****************************************************************************/ // 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_; 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"); } }; // 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 }; private: Which which_; public: Type() = default; MOZ_IMPLICIT Type(Which w) : which_(w) {} // 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: 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: // These types need some kind of coercion, they can't be mapped // to an VarType. 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_CRASH("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(); } // 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()) { case Int: case Float: case Double: case Void: return true; default: return false; } } // 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()) { case Int: return ValType::I32; case Float: return ValType::F32; case Double: return ValType::F64; default: MOZ_CRASH("Need canonical type"); } } Maybe canonicalToReturnType() const { return isVoid() ? Nothing() : Some(canonicalToValType()); } // 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: case Signed: case Unsigned: case Int: case Intish: return TypeCode::I32; case Float: case MaybeFloat: case Floatish: return TypeCode::F32; case DoubleLit: case Double: case MaybeDouble: return TypeCode::F64; case Void: return TypeCode::BlockVoid; } 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 ModuleValidatorShared { public: struct Memory { MemoryUsage usage; uint64_t minLength; uint64_t minPages() const { return DivideRoundingUp(minLength, PageSize); } Memory() = default; }; class Func { TaggedParserAtomIndex name_; uint32_t sigIndex_; uint32_t firstUse_; uint32_t funcDefIndex_; bool defined_; // Available when defined: uint32_t srcBegin_; uint32_t srcEnd_; uint32_t line_; Bytes bytes_; Uint32Vector callSiteLineNums_; public: Func(TaggedParserAtomIndex 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) {} TaggedParserAtomIndex 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; using FuncVector = Vector; class Table { uint32_t sigIndex_; TaggedParserAtomIndex name_; uint32_t firstUse_; uint32_t mask_; bool defined_; public: Table(uint32_t sigIndex, TaggedParserAtomIndex name, uint32_t firstUse, uint32_t mask) : sigIndex_(sigIndex), name_(name), firstUse_(firstUse), mask_(mask), defined_(false) {} Table(Table&& rhs) = delete; uint32_t sigIndex() const { return sigIndex_; } TaggedParserAtomIndex 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; 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) // copies |lit| {} 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; friend class ModuleValidatorShared; template 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_; } Scalar::Type viewType() const { MOZ_ASSERT(which_ == ArrayView || which_ == ArrayViewCtor); 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(TaggedParserAtomIndex name, Scalar::Type type) : name(name), type(type) {} TaggedParserAtomIndex name; Scalar::Type type; }; protected: class HashableSig { uint32_t sigIndex_; const TypeContext& types_; public: HashableSig(uint32_t sigIndex, const TypeContext& types) : sigIndex_(sigIndex), types_(types) {} uint32_t sigIndex() const { return sigIndex_; } const FuncType& funcType() const { return types_[sigIndex_].funcType(); } // Implement HashPolicy: using Lookup = const FuncType&; static HashNumber hash(Lookup l) { return l.hash(nullptr); } static bool match(HashableSig lhs, Lookup rhs) { return FuncType::strictlyEquals(lhs.funcType(), rhs); } }; class NamedSig : public HashableSig { TaggedParserAtomIndex name_; public: NamedSig(TaggedParserAtomIndex name, uint32_t sigIndex, const TypeContext& types) : HashableSig(sigIndex, types), name_(name) {} TaggedParserAtomIndex name() const { return name_; } // Implement HashPolicy: struct Lookup { TaggedParserAtomIndex name; const FuncType& funcType; Lookup(TaggedParserAtomIndex name, const FuncType& funcType) : name(name), funcType(funcType) {} }; static HashNumber hash(Lookup l) { return HashGeneric(TaggedParserAtomIndexHasher::hash(l.name), l.funcType.hash(nullptr)); } static bool match(NamedSig lhs, Lookup rhs) { return lhs.name() == rhs.name && FuncType::strictlyEquals(lhs.funcType(), rhs.funcType); } }; using SigSet = HashSet; using FuncImportMap = HashMap; using GlobalMap = HashMap; using MathNameMap = HashMap; using ArrayViewVector = Vector; protected: FrontendContext* fc_; ParserAtomsTable& parserAtoms_; FunctionNode* moduleFunctionNode_; TaggedParserAtomIndex moduleFunctionName_; TaggedParserAtomIndex globalArgumentName_; TaggedParserAtomIndex importArgumentName_; TaggedParserAtomIndex bufferArgumentName_; MathNameMap standardLibraryMathNames_; // Validation-internal state: LifoAlloc validationLifo_; Memory memory_; FuncVector funcDefs_; TableVector tables_; GlobalMap globalMap_; SigSet sigSet_; FuncImportMap funcImportMap_; ArrayViewVector arrayViews_; // State used to build the AsmJSModule in finish(): CompilerEnvironment compilerEnv_; ModuleEnvironment moduleEnv_; MutableAsmJSMetadata asmJSMetadata_; // Error reporting: UniqueChars errorString_ = nullptr; uint32_t errorOffset_ = UINT32_MAX; bool errorOverRecursed_ = false; protected: ModuleValidatorShared(FrontendContext* fc, ParserAtomsTable& parserAtoms, FunctionNode* moduleFunctionNode) : fc_(fc), parserAtoms_(parserAtoms), moduleFunctionNode_(moduleFunctionNode), moduleFunctionName_(FunctionName(moduleFunctionNode)), standardLibraryMathNames_(fc), validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE), funcDefs_(fc), tables_(fc), globalMap_(fc), sigSet_(fc), funcImportMap_(fc), arrayViews_(fc), compilerEnv_(CompileMode::Once, Tier::Optimized, DebugEnabled::False), moduleEnv_(FeatureArgs(), ModuleKind::AsmJS) { compilerEnv_.computeParameters(); memory_.minLength = RoundUpToNextValidAsmJSHeapLength(0); } protected: [[nodiscard]] bool initModuleEnvironment() { return moduleEnv_.init(); } [[nodiscard]] 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) { auto atom = parserAtoms_.internAscii(fc_, name, strlen(name)); if (!atom) { return false; } MathBuiltin builtin(func); return this->standardLibraryMathNames_.putNew(atom, 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) { auto atom = parserAtoms_.internAscii(fc_, name, strlen(name)); if (!atom) { return false; } MathBuiltin builtin(cst); return this->standardLibraryMathNames_.putNew(atom, builtin); }; for (const auto& info : constants) { if (!AddMathConstant(info.name, info.value)) { return false; } } return true; } public: FrontendContext* fc() const { return fc_; } TaggedParserAtomIndex moduleFunctionName() const { return moduleFunctionName_; } TaggedParserAtomIndex globalArgumentName() const { return globalArgumentName_; } TaggedParserAtomIndex importArgumentName() const { return importArgumentName_; } TaggedParserAtomIndex bufferArgumentName() const { return bufferArgumentName_; } const ModuleEnvironment& env() { return moduleEnv_; } void initModuleFunctionName(TaggedParserAtomIndex name) { MOZ_ASSERT(!moduleFunctionName_); moduleFunctionName_ = name; } [[nodiscard]] bool initGlobalArgumentName(TaggedParserAtomIndex n) { globalArgumentName_ = n; if (n) { asmJSMetadata_->globalArgumentName = parserAtoms_.toNewUTF8CharsZ(fc_, n); if (!asmJSMetadata_->globalArgumentName) { return false; } } return true; } [[nodiscard]] bool initImportArgumentName(TaggedParserAtomIndex n) { importArgumentName_ = n; if (n) { asmJSMetadata_->importArgumentName = parserAtoms_.toNewUTF8CharsZ(fc_, n); if (!asmJSMetadata_->importArgumentName) { return false; } } return true; } [[nodiscard]] bool initBufferArgumentName(TaggedParserAtomIndex n) { bufferArgumentName_ = n; if (n) { asmJSMetadata_->bufferArgumentName = parserAtoms_.toNewUTF8CharsZ(fc_, n); if (!asmJSMetadata_->bufferArgumentName) { return false; } } return true; } bool addGlobalVarInit(TaggedParserAtomIndex var, const NumLit& lit, Type type, bool isConst) { MOZ_ASSERT(type.isGlobalVarType()); MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit))); uint32_t index = moduleEnv_.globals.length(); if (!moduleEnv_.globals.emplaceBack(type.canonicalToValType(), !isConst, index, ModuleKind::AsmJS)) { return false; } Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable; Global* global = validationLifo_.new_(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(TaggedParserAtomIndex var, TaggedParserAtomIndex field, Type type, bool isConst) { MOZ_ASSERT(type.isGlobalVarType()); UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field); if (!fieldChars) { return false; } uint32_t index = moduleEnv_.globals.length(); ValType valType = type.canonicalToValType(); if (!moduleEnv_.globals.emplaceBack(valType, !isConst, index, ModuleKind::AsmJS)) { return false; } Global::Which which = isConst ? Global::ConstantImport : Global::Variable; Global* global = validationLifo_.new_(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(TaggedParserAtomIndex var, Scalar::Type vt, TaggedParserAtomIndex maybeField) { UniqueChars fieldChars; if (maybeField) { fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField); if (!fieldChars) { return false; } } if (!arrayViews_.append(ArrayView(var, vt))) { return false; } Global* global = validationLifo_.new_(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(TaggedParserAtomIndex var, AsmJSMathBuiltinFunction func, TaggedParserAtomIndex field) { UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field); if (!fieldChars) { return false; } Global* global = validationLifo_.new_(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(TaggedParserAtomIndex var, double constant) { Global* global = validationLifo_.new_(Global::ConstantLiteral); if (!global) { return false; } new (&global->u.varOrConst) Global::U::VarOrConst(constant); return globalMap_.putNew(var, global); } public: bool addMathBuiltinConstant(TaggedParserAtomIndex var, double constant, TaggedParserAtomIndex field) { UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, 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(TaggedParserAtomIndex var, double constant, TaggedParserAtomIndex field) { UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, 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(TaggedParserAtomIndex var, Scalar::Type vt, TaggedParserAtomIndex field) { UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field); if (!fieldChars) { return false; } Global* global = validationLifo_.new_(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(TaggedParserAtomIndex var, TaggedParserAtomIndex field) { UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, field); if (!fieldChars) { return false; } if (asmJSMetadata_->numFFIs == UINT32_MAX) { return false; } uint32_t ffiIndex = asmJSMetadata_->numFFIs++; Global* global = validationLifo_.new_(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, TaggedParserAtomIndex maybeField) { // Record the field name of this export. CacheableName fieldName; if (maybeField) { UniqueChars fieldChars = parserAtoms_.toNewUTF8CharsZ(fc_, maybeField); if (!fieldChars) { return false; } 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 (!moduleEnv_.exports.emplaceBack(std::move(fieldName), funcIndex, DefinitionKind::Function)) { return false; } // The exported function might have already been exported in which case // the index will refer into the range of AsmJSExports. 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(); if (!seg) { return false; } seg->elemType = RefType::func(); seg->tableIndex = tableIndex; seg->offsetIfActive = Some(InitExpr(LitVal(uint32_t(0)))); seg->elemFuncIndices = std::move(elems); return moduleEnv_.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 > memory_.minLength) { memory_.minLength = len; } return true; } // Error handling. 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, TaggedParserAtomIndex name) { // This function is invoked without the caller properly rooting its locals. if (UniqueChars bytes = parserAtoms_.toPrintableString(name)) { failfOffset(offset, fmt, bytes.get()); } else { ReportOutOfMemory(fc_); } return false; } bool failName(ParseNode* pn, const char* fmt, TaggedParserAtomIndex 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(TaggedParserAtomIndex name) const { if (GlobalMap::Ptr p = globalMap_.lookup(name)) { return p->value(); } return nullptr; } Func* lookupFuncDef(TaggedParserAtomIndex 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(TaggedParserAtomIndex name, MathBuiltin* mathBuiltin) const { if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) { *mathBuiltin = p->value(); return true; } return false; } bool startFunctionBodies() { if (!arrayViews_.empty()) { memory_.usage = MemoryUsage::Unshared; } else { memory_.usage = MemoryUsage::None; } return true; } }; // 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 class MOZ_STACK_CLASS ModuleValidator : public ModuleValidatorShared { private: AsmJSParser& parser_; public: ModuleValidator(FrontendContext* fc, ParserAtomsTable& parserAtoms, AsmJSParser& parser, FunctionNode* moduleFunctionNode) : ModuleValidatorShared(fc, parserAtoms, moduleFunctionNode), parser_(parser) {} ~ModuleValidator() { if (errorString_) { MOZ_ASSERT(errorOffset_ != UINT32_MAX); typeFailure(errorOffset_, errorString_.get()); } if (errorOverRecursed_) { ReportOverRecursed(fc_); } } private: // Helpers: bool newSig(FuncType&& sig, uint32_t* sigIndex) { if (moduleEnv_.types->length() >= MaxTypes) { return failCurrentOffset("too many signatures"); } *sigIndex = moduleEnv_.types->length(); return moduleEnv_.types->addType(std::move(sig)); } bool declareSig(FuncType&& sig, uint32_t* sigIndex) { SigSet::AddPtr p = sigSet_.lookupForAdd(sig); if (p) { *sigIndex = p->sigIndex(); MOZ_ASSERT(FuncType::strictlyEquals( moduleEnv_.types->type(*sigIndex).funcType(), sig)); return true; } return newSig(std::move(sig), sigIndex) && sigSet_.add(p, HashableSig(*sigIndex, *moduleEnv_.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) { ReportCompileErrorLatin1(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); } } va_end(args); } public: bool init() { asmJSMetadata_ = js_new(); if (!asmJSMetadata_) { ReportOutOfMemory(fc_); return false; } asmJSMetadata_->toStringStart = moduleFunctionNode_->funbox()->extent().toStringStart; asmJSMetadata_->srcStart = moduleFunctionNode_->body()->pn_pos.begin; asmJSMetadata_->strict = parser_.pc_->sc()->strict() && !parser_.pc_->sc()->hasExplicitUseStrict(); asmJSMetadata_->shouldResistFingerprinting = parser_.options().shouldResistFingerprinting(); asmJSMetadata_->source = do_AddRef(parser_.ss); if (!initModuleEnvironment()) { return false; } return addStandardLibraryMathInfo(); } AsmJSParser& parser() const { return parser_; } auto& tokenStream() const { return parser_.tokenStream; } bool shouldResistFingerprinting() const { return asmJSMetadata_->shouldResistFingerprinting; } public: bool addFuncDef(TaggedParserAtomIndex 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::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, TaggedParserAtomIndex name, uint32_t firstUse, uint32_t mask, uint32_t* tableIndex) { if (mask > MaxTableLength) { return failCurrentOffset("function pointer table too big"); } MOZ_ASSERT(moduleEnv_.tables.length() == tables_.length()); *tableIndex = moduleEnv_.tables.length(); uint32_t sigIndex; if (!newSig(std::move(sig), &sigIndex)) { return false; } MOZ_ASSERT(sigIndex >= moduleEnv_.asmJSSigToTableIndex.length()); if (!moduleEnv_.asmJSSigToTableIndex.resize(sigIndex + 1)) { return false; } moduleEnv_.asmJSSigToTableIndex[sigIndex] = moduleEnv_.tables.length(); if (!moduleEnv_.tables.emplaceBack(RefType::func(), mask + 1, Nothing(), /* initExpr */ Nothing(), /*isAsmJS*/ true)) { return false; } Global* global = validationLifo_.new_(Global::Table); if (!global) { return false; } new (&global->u.tableIndex_) uint32_t(*tableIndex); if (!globalMap_.putNew(name, global)) { return false; } Table* t = validationLifo_.new_(sigIndex, name, firstUse, mask); return t && tables_.append(t); } bool declareImport(TaggedParserAtomIndex 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, *moduleEnv_.types), *importIndex); } // Error handling. bool failCurrentOffset(const char* str) { return failOffset(tokenStream().anyCharsAccess().currentToken().pos.begin, str); } SharedModule finish() { MOZ_ASSERT(!moduleEnv_.usesMemory()); if (memory_.usage != MemoryUsage::None) { Limits limits; limits.shared = memory_.usage == MemoryUsage::Shared ? Shareable::True : Shareable::False; limits.initial = memory_.minPages(); limits.maximum = Nothing(); limits.indexType = IndexType::I32; moduleEnv_.memory = Some(MemoryDesc(limits)); } MOZ_ASSERT(moduleEnv_.funcs.empty()); if (!moduleEnv_.funcs.resize(funcImportMap_.count() + funcDefs_.length())) { return nullptr; } for (FuncImportMap::Range r = funcImportMap_.all(); !r.empty(); r.popFront()) { uint32_t funcIndex = r.front().value(); uint32_t funcTypeIndex = r.front().key().sigIndex(); MOZ_ASSERT(!moduleEnv_.funcs[funcIndex].type); moduleEnv_.funcs[funcIndex] = FuncDesc( &moduleEnv_.types->type(funcTypeIndex).funcType(), funcTypeIndex); } for (const Func& func : funcDefs_) { uint32_t funcIndex = funcImportMap_.count() + func.funcDefIndex(); uint32_t funcTypeIndex = func.sigIndex(); MOZ_ASSERT(!moduleEnv_.funcs[funcIndex].type); moduleEnv_.funcs[funcIndex] = FuncDesc( &moduleEnv_.types->type(funcTypeIndex).funcType(), funcTypeIndex); } for (const Export& exp : moduleEnv_.exports) { if (exp.kind() != DefinitionKind::Function) { continue; } uint32_t funcIndex = exp.funcIndex(); moduleEnv_.declareFuncExported(funcIndex, /* eager */ true, /* canRefFunc */ false); } moduleEnv_.numFuncImports = funcImportMap_.count(); MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty()); if (!asmJSMetadata_->asmJSFuncNames.resize(funcImportMap_.count())) { return nullptr; } for (const Func& func : funcDefs_) { CacheableChars funcName = parserAtoms_.toNewUTF8CharsZ(fc_, 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::SlashIsRegExp)); uint32_t endAfterCurly = pos.end; asmJSMetadata_->srcLengthWithRightBrace = endAfterCurly - asmJSMetadata_->srcStart; ScriptedCaller scriptedCaller; if (parser_.ss->filename()) { scriptedCaller.line = 0; // unused scriptedCaller.filename = DuplicateString(parser_.ss->filename()); if (!scriptedCaller.filename) { return nullptr; } } // The default options are fine for asm.js SharedCompileArgs args = CompileArgs::buildForAsmJS(std::move(scriptedCaller)); if (!args) { ReportOutOfMemory(fc_); return nullptr; } uint32_t codeSectionSize = 0; for (const Func& func : funcDefs_) { codeSectionSize += func.bytes().length(); } moduleEnv_.codeSection.emplace(); moduleEnv_.codeSection->start = 0; moduleEnv_.codeSection->size = codeSectionSize; // asm.js does not have any wasm bytecode to save; view-source is // provided through the ScriptSource. SharedBytes bytes = js_new(); if (!bytes) { ReportOutOfMemory(fc_); return nullptr; } ModuleGenerator mg(*args, &moduleEnv_, &compilerEnv_, nullptr, 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); } }; /*****************************************************************************/ // Numeric literal utilities static bool 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)); } 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().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; } // Don't fold into || to avoid clang/memcheck bug (bug 1077031). if (!coerceTo.isFloat()) { return false; } return IsNumericNonFloatLiteral(coercedExpr); } static bool IsNumericLiteral(ModuleValidatorShared& m, ParseNode* pn) { return IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn); } // 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. 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)) { // 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)); } 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_CRASH("Bad literal type"); } static inline bool IsLiteralInt(ModuleValidatorShared& m, ParseNode* pn, uint32_t* u32) { return IsNumericLiteral(m, pn) && IsLiteralInt(ExtractNumericLiteral(m, pn), u32); } /*****************************************************************************/ namespace { using LabelVector = Vector; 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; using LabelMap = HashMap; // This is also a ModuleValidator& after the appropriate static_cast<>. ModuleValidatorShared& m_; FunctionNode* fn_; Bytes bytes_; Encoder encoder_; Uint32Vector callSiteLineNums_; LocalMap locals_; // Labels LabelMap breakLabels_; LabelMap continueLabels_; Uint32Vector breakableStack_; Uint32Vector continuableStack_; uint32_t blockDepth_; bool hasAlreadyReturned_; Maybe ret_; private: FunctionValidatorShared(ModuleValidatorShared& m, FunctionNode* fn, FrontendContext* fc) : m_(m), fn_(fn), encoder_(bytes_), locals_(fc), breakLabels_(fc), continueLabels_(fc), blockDepth_(0), hasAlreadyReturned_(false) {} protected: template FunctionValidatorShared(ModuleValidator& m, FunctionNode* fn, FrontendContext* fc) : FunctionValidatorShared(static_cast(m), fn, fc) {} public: ModuleValidatorShared& m() const { return m_; } FrontendContext* fc() const { return m_.fc(); } FunctionNode* 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, TaggedParserAtomIndex name) { return m_.failName(pn, fmt, name); } /***************************************************** Local scope setup */ bool addLocal(ParseNode* pn, TaggedParserAtomIndex 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())); } /****************************** For consistency of returns in a function */ bool hasAlreadyReturned() const { return hasAlreadyReturned_; } Maybe returnedType() const { return ret_; } void setReturnedType(const Maybe& ret) { MOZ_ASSERT(!hasAlreadyReturned_); ret_ = ret; hasAlreadyReturned_ = true; } /**************************************************************** Labels */ 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(TaggedParserAtomIndex 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(TypeCode::BlockVoid)) && 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 (TaggedParserAtomIndex label : *labels) { if (!breakLabels_.putNew(label, blockDepth_)) { return false; } } } blockDepth_++; return encoder().writeOp(Op::Block) && encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)); } bool popUnbreakableBlock(const LabelVector* labels = nullptr) { if (labels) { for (TaggedParserAtomIndex label : *labels) { removeLabel(label, &breakLabels_); } } --blockDepth_; return encoder().writeOp(Op::End); } bool pushContinuableBlock() { return encoder().writeOp(Op::Block) && encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) && 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(TypeCode::BlockVoid)) && encoder().writeOp(Op::Loop) && encoder().writeFixedU8(uint8_t(TypeCode::BlockVoid)) && 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, TypeCode type) { encoder().patchFixedU7(typeAt, uint8_t(type)); } bool popIf() { MOZ_ASSERT(blockDepth_ > 0); --blockDepth_; return encoder().writeOp(Op::End); } bool popIf(size_t typeAt, TypeCode 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 (TaggedParserAtomIndex 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 (TaggedParserAtomIndex label : labels) { removeLabel(label, &breakLabels_); removeLabel(label, &continueLabels_); } } bool writeLabeledBreakOrContinue(TaggedParserAtomIndex label, bool isBreak) { LabelMap& map = isBreak ? breakLabels_ : continueLabels_; if (LabelMap::Ptr p = map.lookup(label)) { return writeBr(p->value()); } MOZ_CRASH("nonexistent label"); } /*************************************************** Read-only interface */ const Local* lookupLocal(TaggedParserAtomIndex name) const { if (auto p = locals_.lookup(name)) { return &p->value(); } return nullptr; } const ModuleValidatorShared::Global* lookupGlobal( TaggedParserAtomIndex name) const { if (locals_.has(name)) { return nullptr; } return m_.lookupGlobal(name); } size_t numLocals() const { return locals_.count(); } /**************************************************** Encoding interface */ Encoder& encoder() { return encoder_; } [[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 class MOZ_STACK_CLASS FunctionValidator : public FunctionValidatorShared { public: FunctionValidator(ModuleValidator& m, FunctionNode* fn) : FunctionValidatorShared(m, fn, m.fc()) {} public: ModuleValidator& m() const { return static_cast&>(FunctionValidatorShared::m()); } [[nodiscard]] bool writeCall(ParseNode* pn, Op op) { MOZ_ASSERT(op == Op::Call); if (!encoder().writeOp(op)) { return false; } return appendCallSiteLineNumber(pn); } [[nodiscard]] bool writeCall(ParseNode* pn, MozOp op) { MOZ_ASSERT(op == MozOp::OldCallDirect || op == MozOp::OldCallIndirect); if (!encoder().writeOp(op)) { return false; } return appendCallSiteLineNumber(pn); } [[nodiscard]] bool prepareCall(ParseNode* pn) { return appendCallSiteLineNumber(pn); } private: [[nodiscard]] bool appendCallSiteLineNumber(ParseNode* node) { const TokenStreamAnyChars& anyChars = m().tokenStream().anyCharsAccess(); auto lineToken = anyChars.lineToken(node->pn_pos.begin); uint32_t lineNumber = anyChars.lineNumber(lineToken); if (lineNumber > CallSiteDesc::MAX_LINE_OR_BYTECODE_VALUE) { return fail(node, "line number exceeding implementation limits"); } return callSiteLineNums_.append(lineNumber); } }; } /* anonymous namespace */ /*****************************************************************************/ // asm.js type-checking and code-generation algorithm static bool 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); } return true; } static bool CheckModuleLevelName(ModuleValidatorShared& m, ParseNode* usepn, TaggedParserAtomIndex 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, TaggedParserAtomIndex* name) { *name = TaggedParserAtomIndex::null(); if (!arg->isKind(ParseNodeKind::Name)) { return m.fail(arg, "argument is not a plain name"); } TaggedParserAtomIndex argName = arg->as().name(); if (!CheckIdentifier(m, arg, argName)) { return false; } *name = argName; return true; } static bool CheckModuleArgument(ModuleValidatorShared& m, ParseNode* arg, TaggedParserAtomIndex* 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"); } TaggedParserAtomIndex arg1Name; if (arg1 && !CheckModuleArgument(m, arg1, &arg1Name)) { return false; } if (!m.initGlobalArgumentName(arg1Name)) { return false; } TaggedParserAtomIndex arg2Name; if (arg2 && !CheckModuleArgument(m, arg2, &arg2Name)) { return false; } if (!m.initImportArgumentName(arg2Name)) { return false; } TaggedParserAtomIndex arg3Name; 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(stmt)) { return m.fail(stmt, "invalid asm.js statement"); } } return true; } static bool 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"); } 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, TaggedParserAtomIndex 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); 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); } return m.addGlobalVarImport(varName, field, coerceTo, isConst); } static bool IsArrayViewCtorName(ModuleValidatorShared& m, TaggedParserAtomIndex name, Scalar::Type* type) { if (name == TaggedParserAtomIndex::WellKnown::Int8Array()) { *type = Scalar::Int8; } else if (name == TaggedParserAtomIndex::WellKnown::Uint8Array()) { *type = Scalar::Uint8; } else if (name == TaggedParserAtomIndex::WellKnown::Int16Array()) { *type = Scalar::Int16; } else if (name == TaggedParserAtomIndex::WellKnown::Uint16Array()) { *type = Scalar::Uint16; } else if (name == TaggedParserAtomIndex::WellKnown::Int32Array()) { *type = Scalar::Int32; } else if (name == TaggedParserAtomIndex::WellKnown::Uint32Array()) { *type = Scalar::Uint32; } else if (name == TaggedParserAtomIndex::WellKnown::Float32Array()) { *type = Scalar::Float32; } else if (name == TaggedParserAtomIndex::WellKnown::Float64Array()) { *type = Scalar::Float64; } else { return false; } return true; } static bool CheckNewArrayViewArgs(ModuleValidatorShared& m, ParseNode* newExpr, TaggedParserAtomIndex 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, 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().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)) { return false; } return m.addArrayView(varName, type, field); } static bool 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"); } static bool 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); } if (math == TaggedParserAtomIndex::WellKnown::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().name(); if (baseName == m.globalArgumentName()) { if (field == TaggedParserAtomIndex::WellKnown::NaN()) { return m.addGlobalConstant(varName, GenericNaN(), field); } if (field == TaggedParserAtomIndex::WellKnown::Infinity()) { return m.addGlobalConstant(varName, PositiveInfinity(), 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(); ParseNode* var = assignNode->left(); if (!var->isKind(ParseNodeKind::Name)) { return m.fail(var, "import variable is not a plain name"); } TaggedParserAtomIndex varName = var->as().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 static bool CheckModuleProcessingDirectives(ModuleValidator& m) { auto& ts = m.parser().tokenStream; while (true) { bool matched; if (!ts.matchToken(&matched, TokenKind::String, TokenStreamShared::SlashIsRegExp)) { return false; } if (!matched) { return true; } if (!IsIgnoredDirectiveName(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 static bool CheckModuleGlobals(ModuleValidator& 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, 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); } static bool CheckArgumentType(FunctionValidatorShared& f, ParseNode* stmt, TaggedParserAtomIndex 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(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)) { TaggedParserAtomIndex 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().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(Nothing()); return true; } if (!lastNonEmptyStmt->isKind(ParseNodeKind::ReturnStmt) && f.returnedType()) { return f.fail(lastNonEmptyStmt, "void incompatible with previous return type"); } return true; } static bool CheckVariable(FunctionValidatorShared& f, ParseNode* decl, ValTypeVector* types, Vector* inits) { if (!decl->isKind(ParseNodeKind::AssignExpr)) { return f.failName( decl, "var '%s' needs explicit type declaration via an initial value", decl->as().name()); } AssignmentNode* assignNode = &decl->as(); ParseNode* var = assignNode->left(); if (!var->isKind(ParseNodeKind::Name)) { return f.fail(var, "local variable is not a plain name"); } TaggedParserAtomIndex name = var->as().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 inits(f.fc()); 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::LocalSet)) { return false; } if (!f.encoder().writeVarU32(firstVar + i)) { return false; } } *stmtIter = stmt; return true; } template static bool CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type); template static bool CheckNumericLiteral(FunctionValidator& 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) { TaggedParserAtomIndex name = varRef->as().name(); if (const FunctionValidatorShared::Local* local = f.lookupLocal(name)) { if (!f.encoder().writeOp(Op::LocalGet)) { 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::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); } 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 static bool CheckArrayAccess(FunctionValidator& 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().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)) { return false; } 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)) { return false; } 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); } return true; } static bool WriteArrayAccessFlags(FunctionValidatorShared& f, Scalar::Type viewType) { // asm.js only has naturally-aligned accesses. size_t align = TypedArrayElemSize(viewType); MOZ_ASSERT(IsPowerOfTwo(align)); if (!f.encoder().writeFixedU8(CeilingLog2(align))) { return false; } // asm.js doesn't have constant offsets, so just encode a 0. if (!f.encoder().writeVarU32(0)) { return false; } return true; } template static bool CheckLoadArray(FunctionValidator& 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"); } return WriteArrayAccessFlags(f, viewType); } template static bool CheckStoreArray(FunctionValidator& 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 static bool CheckAssignName(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type) { TaggedParserAtomIndex name = lhs->as().name(); if (const FunctionValidatorShared::Local* lhsVar = f.lookupLocal(name)) { Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) { return false; } if (!f.encoder().writeOp(Op::LocalTee)) { 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 static bool CheckAssign(FunctionValidator& 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 static bool CheckMathIMul(FunctionValidator& 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 static bool CheckMathClz32(FunctionValidator& 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 static bool CheckMathAbs(FunctionValidator& 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 static bool CheckMathSqrt(FunctionValidator& 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 static bool CheckMathMinMax(FunctionValidator& 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 static bool CheckCallArgs(FunctionValidator& 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 (!FuncType::strictlyEquals(sig, existing)) { return m.failf(usepn, "incompatible argument types to function"); } return true; } template static bool CheckFunctionSignature(ModuleValidator& m, ParseNode* usepn, FuncType&& sig, TaggedParserAtomIndex 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 FuncType& existingSig = m.env().types->type(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 static bool CheckInternalCall(FunctionValidator& f, ParseNode* callNode, TaggedParserAtomIndex calleeName, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); ValTypeVector args; if (!CheckCallArgs(f, callNode, &args)) { return false; } ValTypeVector results; Maybe retType = ret.canonicalToReturnType(); if (retType && !results.append(retType.ref())) { return false; } FuncType sig(std::move(args), std::move(results)); 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 static bool CheckFuncPtrTableAgainstExisting(ModuleValidator& 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.env().types->type(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 static bool CheckFuncPtrCall(FunctionValidator& 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"); } TaggedParserAtomIndex name = tableNode->as().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(f, callNode, &args)) { return false; } ValTypeVector results; Maybe retType = ret.canonicalToReturnType(); if (retType && !results.append(retType.ref())) { return false; } FuncType sig(std::move(args), std::move(results)); uint32_t tableIndex; if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, std::move(sig), mask, &tableIndex)) { return false; } if (!f.writeCall(callNode, MozOp::OldCallIndirect)) { return false; } // Call signature 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 static bool CheckFFICall(FunctionValidator& f, ParseNode* callNode, unsigned ffiIndex, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); TaggedParserAtomIndex calleeName = CallCallee(callNode)->as().name(); if (ret.isFloat()) { return f.fail(callNode, "FFI calls can't return float"); } ValTypeVector args; if (!CheckCallArgs(f, callNode, &args)) { return false; } ValTypeVector results; Maybe retType = ret.canonicalToReturnType(); if (retType && !results.append(retType.ref())) { return false; } FuncType sig(std::move(args), std::move(results)); 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::F32ConvertI32S); } if (inputType.isUnsigned()) { return f.encoder().writeOp(Op::F32ConvertI32U); } if (inputType.isFloatish()) { return true; } return f.failf(inputNode, "%s is not a subtype of signed, unsigned, double? or floatish", inputType.toChars()); } template static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type); template static bool CheckCoercionArg(FunctionValidator& 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 static bool CheckMathFRound(FunctionValidator& 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 static bool CheckMathBuiltinCall(FunctionValidator& 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, /* isMax = */ false, type); case AsmJSMathBuiltin_max: return CheckMathMinMax(f, callNode, /* isMax = */ 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; if (!f.m().shouldResistFingerprinting()) { mozf64 = MozOp::F64SinNative; } else { mozf64 = MozOp::F64SinFdlibm; } f32 = Op::Unreachable; break; case AsmJSMathBuiltin_cos: arity = 1; if (!f.m().shouldResistFingerprinting()) { mozf64 = MozOp::F64CosNative; } else { mozf64 = MozOp::F64CosFdlibm; } f32 = Op::Unreachable; break; case AsmJSMathBuiltin_tan: arity = 1; if (!f.m().shouldResistFingerprinting()) { mozf64 = MozOp::F64TanNative; } else { mozf64 = MozOp::F64TanFdlibm; } 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 static bool CheckUncoercedCall(FunctionValidator& 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()); // 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)) { 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()) { // No conversion necessary. } else if (actual.isMaybeFloat()) { if (!f.encoder().writeOp(Op::F64PromoteF32)) { return false; } } else if (actual.isSigned()) { if (!f.encoder().writeOp(Op::F64ConvertI32S)) { return false; } } else if (actual.isUnsigned()) { if (!f.encoder().writeOp(Op::F64ConvertI32U)) { 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 static bool CheckCoercedMathBuiltinCall(FunctionValidator& 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 static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); AutoCheckRecursionLimit recursion(f.fc()); if (!recursion.checkDontReport(f.fc())) { 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"); } TaggedParserAtomIndex calleeName = callee->as().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 static bool CheckPos(FunctionValidator& 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 static bool CheckNot(FunctionValidator& 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 static bool CheckNeg(FunctionValidator& 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 static bool CheckCoerceToInt(FunctionValidator& 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::I32TruncF64S : Op::I32TruncF32S; 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 static bool CheckBitNot(FunctionValidator& 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 static bool CheckAsExprStatement(FunctionValidator& f, ParseNode* exprStmt); template static bool CheckComma(FunctionValidator& f, ParseNode* comma, Type* type) { MOZ_ASSERT(comma->isKind(ParseNodeKind::CommaExpr)); ParseNode* operands = ListHead(comma); // 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)) { 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())); return f.encoder().writeOp(Op::End); } template static bool CheckConditional(FunctionValidator& 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 static bool IsValidIntMultiplyConstant(ModuleValidator& 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_CRASH("Bad literal"); } template static bool CheckMultiply(FunctionValidator& 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 static bool CheckAddOrSub(FunctionValidator& f, ParseNode* expr, Type* type, unsigned* numAddOrSubOut = nullptr) { AutoCheckRecursionLimit recursion(f.fc()); if (!recursion.checkDontReport(f.fc())) { 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 static bool CheckDivOrMod(FunctionValidator& 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); } 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 static bool CheckComparison(FunctionValidator& 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 static bool CheckBitwise(FunctionValidator& 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 static bool CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type) { AutoCheckRecursionLimit recursion(f.fc()); if (!recursion.checkDontReport(f.fc())) { 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 static bool CheckStatement(FunctionValidator& f, ParseNode* stmt); template static bool CheckAsExprStatement(FunctionValidator& 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 static bool CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt) { MOZ_ASSERT(exprStmt->isKind(ParseNodeKind::ExpressionStmt)); return CheckAsExprStatement(f, UnaryKid(exprStmt)); } template static bool CheckLoopConditionOnEntry(FunctionValidator& 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; } // brIf (i32.eqz $f) $out if (!f.writeBreakIf()) { return false; } return true; } template static bool CheckWhile(FunctionValidator& f, ParseNode* whileStmt, const LabelVector* labels = nullptr) { MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::WhileStmt)); ParseNode* cond = BinaryLeft(whileStmt); ParseNode* body = BinaryRight(whileStmt); // A while loop `while(#cond) #body` is equivalent to: // (block $after_loop // (loop $top // (brIf $after_loop (i32.eq 0 #cond)) // #body // (br $top) // ) // ) 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 static bool CheckFor(FunctionValidator& 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); // 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)) { 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; } { // Continuing in the body should just break out to the increment. 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 static bool CheckDoWhile(FunctionValidator& f, ParseNode* whileStmt, const LabelVector* labels = nullptr) { MOZ_ASSERT(whileStmt->isKind(ParseNodeKind::DoWhileStmt)); ParseNode* body = BinaryLeft(whileStmt); ParseNode* cond = BinaryRight(whileStmt); // 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)) { return false; } if (!f.pushLoop()) { return false; } { // An unlabeled continue in the body should break out to the condition. 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 static bool CheckStatementList(FunctionValidator& f, ParseNode*, const LabelVector* = nullptr); template static bool CheckLabel(FunctionValidator& 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 static bool CheckIf(FunctionValidator& 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, TypeCode::BlockVoid); 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 = std::min(*low, i); *high = std::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 static bool CheckSwitchExpr(FunctionValidator& 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; } // 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 static bool CheckSwitch(FunctionValidator& f, ParseNode* switchStmt) { MOZ_ASSERT(switchStmt->isKind(ParseNodeKind::SwitchStmt)); ParseNode* switchExpr = BinaryLeft(switchStmt); ParseNode* switchBody = BinaryRight(switchStmt); if (switchBody->is()) { LexicalScopeNode* scope = &switchBody->as(); 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++; } // Open the wrapping breakable default block. if (!f.pushBreakableBlock()) { return false; } // Open all the case blocks. for (uint32_t i = 0; i < numCases; i++) { if (!f.pushUnbreakableBlock()) { return false; } } // Open the br_table block. if (!f.pushUnbreakableBlock()) { return false; } // 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)) { return false; } if (!f.writeInt32Lit(low)) { return false; } if (!f.encoder().writeOp(Op::I32Sub)) { return false; } } else { if (!CheckSwitchExpr(f, switchExpr)) { return false; } } // Start the br_table block. if (!f.encoder().writeOp(Op::BrTable)) { return false; } // Write the number of cases (tableLength - 1 + 1 (default)). // Write the number of cases (tableLength - 1 + 1 (default)). if (!f.encoder().writeVarU32(tableLength)) { return false; } // 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)) { return false; } } // Write the default depth. if (!f.encoder().writeVarU32(defaultDepth)) { return false; } // Our br_table is done. Close its block, write the cases down in order. if (!f.popUnbreakableBlock()) { return false; } for (; stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) { if (!CheckStatement(f, CaseBody(stmt))) { return false; } if (!f.popUnbreakableBlock()) { return false; } } // Write the default block. if (stmt && IsDefaultCase(stmt)) { if (!CheckStatement(f, CaseBody(stmt))) { return false; } } // Close the wrapping block. return f.popBreakableBlock(); } static bool CheckReturnType(FunctionValidatorShared& f, ParseNode* usepn, Type ret) { Maybe type = ret.canonicalToReturnType(); if (!f.hasAlreadyReturned()) { f.setReturnedType(type); return true; } 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()); } return true; } template static bool CheckReturn(FunctionValidator& 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; } } return f.encoder().writeOp(Op::Return); } template static bool CheckStatementList(FunctionValidator& f, ParseNode* stmtList, const LabelVector* labels /*= nullptr */) { 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; } } return f.popUnbreakableBlock(labels); } template static bool CheckLexicalScope(FunctionValidator& f, ParseNode* node) { LexicalScopeNode* lexicalScope = &node->as(); 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 (TaggedParserAtomIndex maybeLabel = LoopControlMaybeLabel(stmt)) { return f.writeLabeledBreakOrContinue(maybeLabel, isBreak); } return f.writeUnlabeledBreakOrContinue(isBreak); } template static bool CheckStatement(FunctionValidator& f, ParseNode* stmt) { AutoCheckRecursionLimit recursion(f.fc()); if (!recursion.checkDontReport(f.fc())) { 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 static bool ParseFunction(ModuleValidator& m, FunctionNode** funNodeOut, unsigned* line) { auto& tokenStream = m.tokenStream(); tokenStream.consumeKnownToken(TokenKind::Function, TokenStreamShared::SlashIsRegExp); auto& anyChars = tokenStream.anyCharsAccess(); uint32_t toStringStart = anyChars.currentToken().pos.begin; *line = anyChars.lineNumber(anyChars.lineToken(toStringStart)); TokenKind tk; if (!tokenStream.getToken(&tk, TokenStreamShared::SlashIsRegExp)) { return false; } if (tk == TokenKind::Mul) { return m.failCurrentOffset("unexpected generator function"); } if (!TokenKindIsPossibleIdentifier(tk)) { return false; // The regular parser will throw a SyntaxError, no need to // m.fail. } TaggedParserAtomIndex name = m.parser().bindingIdentifier(YieldIsName); if (!name) { return false; } FunctionNode* funNode = m.parser().handler_.newFunction( FunctionSyntaxKind::Statement, m.parser().pos()); if (!funNode) { return false; } ParseContext* outerpc = m.parser().pc_; Directives directives(outerpc); FunctionFlags flags(FunctionFlags::INTERPRETED_NORMAL); FunctionBox* funbox = m.parser().newFunctionBox( funNode, name, flags, 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 static bool CheckFunction(ModuleValidator& 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)) { return false; } if (!CheckFunctionHead(m, funNode)) { return false; } FunctionValidator 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; } ValTypeVector results; if (f.returnedType()) { if (!results.append(f.returnedType().ref())) { return false; } } FuncType sig(std::move(args), std::move(results)); ModuleValidatorShared::Func* func = nullptr; if (!CheckFunctionSignature(m, funNode, std::move(sig), 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 static bool CheckFunctions(ModuleValidator& 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 static bool CheckFuncPtrTable(ModuleValidator& m, ParseNode* decl) { if (!decl->isKind(ParseNodeKind::AssignExpr)) { return m.fail(decl, "function-pointer table must have initializer"); } AssignmentNode* assignNode = &decl->as(); 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"); } TaggedParserAtomIndex funcName = elem->as().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->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())) { return false; } } FuncType copy; if (!copy.clone(*sig)) { return false; } uint32_t tableIndex; if (!CheckFuncPtrTableAgainstExisting(m, var, var->as().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 static bool CheckFuncPtrTables(ModuleValidator& 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, TaggedParserAtomIndex maybeFieldName = TaggedParserAtomIndex::null()) { if (!pn->isKind(ParseNodeKind::Name)) { return m.fail(pn, "expected name of exported function"); } TaggedParserAtomIndex funcName = pn->as().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"); } TaggedParserAtomIndex 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 static bool CheckModuleReturn(ModuleValidator& 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 static bool CheckModuleEnd(ModuleValidator& 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 static SharedModule CheckModule(FrontendContext* fc, ParserAtomsTable& parserAtoms, AsmJSParser& parser, ParseNode* stmtList, unsigned* time) { int64_t before = PRMJ_Now(); FunctionNode* moduleFunctionNode = parser.pc_->functionBox()->functionNode; ModuleValidator m(fc, parserAtoms, 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; } /*****************************************************************************/ // Link-time validation static bool LinkFail(JSContext* cx, const char* str) { WarnNumberASCII(cx, 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, Handle 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"); } RootedId id(cx, AtomToId(field)); Rooted> desc(cx); RootedObject holder(cx); if (!GetPropertyDescriptor(cx, obj, id, &desc, &holder)) { return false; } if (!desc.isSome()) { 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) { Rooted field(cx, AtomizeUTF8Chars(cx, fieldChars, strlen(fieldChars))); if (!field) { return false; } return GetDataProperty(cx, objVal, field, v); } static bool GetDataProperty(JSContext* cx, HandleValue objVal, const ImmutableTenuredPtr& field, MutableHandleValue v) { Handle 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) { // 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() && HasNoToPrimitiveMethodPure(&v.toObject(), cx) && HasObjectValueOfMethodPure(&v.toObject(), cx) && HasNativeMethodPure(&v.toObject(), cx->names().toString, fun_toString, cx); } static bool ValidateGlobalVariable(JSContext* cx, const AsmJSGlobal& global, HandleValue importVal, Maybe* 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().kind()) { 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::V128: MOZ_CRASH("v128"); 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: { MOZ_CRASH("not available in asm.js"); } } } } MOZ_CRASH("unreachable"); } static bool ValidateFFI(JSContext* cx, const AsmJSGlobal& global, HandleValue importVal, MutableHandle 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()); return true; } static bool ValidateArrayView(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { if (!global.field()) { return true; } if (Scalar::isBigIntType(global.viewType())) { return LinkFail(cx, "bad typed array constructor"); } 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 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"); } static bool ValidateMathBuiltinFunction(JSContext* cx, const AsmJSMetadata& metadata, 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; } InlinableNative native = ToInlinableNative(global.mathBuiltinFunction()); JSFunction* fun; if (!IsFunctionObject(v, &fun) || !fun->hasJitInfo() || fun->jitInfo()->type() != JSJitInfo::InlinableNative || fun->jitInfo()->inlinableNative != native) { return LinkFail(cx, "bad Math.* builtin function"); } if (fun->realm()->behaviors().shouldResistFingerprinting() != metadata.shouldResistFingerprinting) { return LinkFail(cx, "Math.* builtin function and asm.js module have a " "different resist fingerprinting mode"); } 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"); } // 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"); } } return true; } static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata, HandleValue bufferVal, MutableHandle buffer) { if (!bufferVal.isObject()) { return LinkFail(cx, "buffer must be an object"); } JSObject* bufferObj = &bufferVal.toObject(); if (metadata.usesSharedMemory()) { if (!bufferObj->is()) { 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()) { return LinkFail(cx, "unshared views can only be constructed onto ArrayBuffer"); } buffer.set(&bufferObj->as()); 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) { return false; } 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 = metadata.usesMemory() ? metadata.memory->initialLength32() : 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) { return false; } 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) { return false; } return LinkFail(cx, msg.get()); } if (!buffer->prepareForAsmJS()) { return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); } MOZ_ASSERT(buffer->isPreparedForAsmJS()); return true; } static bool GetImports(JSContext* cx, const AsmJSMetadata& metadata, HandleValue globalVal, HandleValue importVal, ImportValues* imports) { Rooted ffis(cx, FunctionVector(cx)); if (!ffis.resize(metadata.numFFIs)) { return false; } for (const AsmJSGlobal& global : metadata.asmJSGlobals) { switch (global.which()) { case AsmJSGlobal::Variable: { Maybe litVal; if (!ValidateGlobalVariable(cx, global, importVal, &litVal)) { return false; } if (!imports->globalValues.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, metadata, global, globalVal)) { return false; } break; case AsmJSGlobal::Constant: if (!ValidateConstant(cx, global, globalVal)) { return false; } break; } } for (const AsmJSImport& import : metadata.asmJSImports) { if (!imports->funcs.append(ffis[import.ffiIndex()])) { return false; } } return true; } static bool TryInstantiate(JSContext* cx, CallArgs args, const Module& module, const AsmJSMetadata& metadata, MutableHandle instanceObj, MutableHandleObject exportObj) { HandleValue globalVal = args.get(0); HandleValue importVal = args.get(1); HandleValue bufferVal = args.get(2); // Re-check HasPlatformSupport(cx) since this varies per-thread and // 'module' may have been produced on a parser thread. if (!HasPlatformSupport(cx)) { return LinkFail(cx, "no platform support"); } Rooted imports(cx); if (module.metadata().usesMemory()) { RootedArrayBufferObject buffer(cx); if (!CheckBuffer(cx, metadata, bufferVal, &buffer)) { return false; } imports.get().memory = WasmMemoryObject::create(cx, buffer, /* isHuge= */ false, nullptr); if (!imports.get().memory) { return false; } } if (!GetImports(cx, metadata, globalVal, importVal, imports.address())) { return false; } if (!module.instantiate(cx, imports.get(), nullptr, instanceObj)) { return false; } exportObj.set(&instanceObj->exportsObj()); return true; } static bool HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata) { using js::frontend::FunctionSyntaxKind; Rooted name(cx, args.callee().as().explicitName()); if (cx->isExceptionPending()) { return false; } ScriptSource* source = metadata.maybeScriptSource(); // 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)) { 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 src(cx, source->substringDontDeflate(cx, begin, end)); if (!src) { return false; } JS::CompileOptions options(cx); options.setMutedErrors(source->mutedErrors()) .setFile(source->filename()) .setNoScriptRval(false); options.asmJSOption = AsmJSOption::DisabledByLinker; // The exported function inherits an implicit strict context if the module // also inherited it somehow. if (metadata.strict) { options.setForceStrictMode(); } AutoStableStringChars linearChars(cx); if (!linearChars.initTwoByte(cx, src)) { return false; } SourceText srcBuf; if (!srcBuf.initMaybeBorrowed(cx, linearChars)) { return false; } FunctionSyntaxKind syntaxKind = FunctionSyntaxKind::Statement; RootedFunction fun(cx, frontend::CompileStandaloneFunction( cx, options, srcBuf, Nothing(), syntaxKind)); if (!fun) { return false; } fun->initEnvironment(&cx->global()->lexicalEnvironment()); // Call the function we just recompiled. 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().module(); } // 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); JSFunction* callee = &args.callee().as(); const Module& module = AsmJSModuleFunctionToModule(callee); const AsmJSMetadata& metadata = module.metadata().asAsmJS(); Rooted instanceObj(cx); RootedObject exportObj(cx); if (!TryInstantiate(cx, args, module, metadata, &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, metadata); } args.rval().set(ObjectValue(*exportObj)); return true; } /*****************************************************************************/ // Top-level js::CompileAsmJS static bool NoExceptionPending(FrontendContext* fc) { return !fc->hadErrors(); } static bool SuccessfulValidation(frontend::ParserBase& parser, unsigned compilationTime) { unsigned errNum = js::SupportDifferentialTesting() ? JSMSG_USE_ASM_TYPE_OK_NO_TIME : JSMSG_USE_ASM_TYPE_OK; 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; } // 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 : ""); return false; } // 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. static bool IsAsmJSCompilerAvailable(JSContext* cx) { return HasPlatformSupport(cx) && WasmCompilerForAsmJSAvailable(cx); } static bool 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"); } return true; } template static bool DoCompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms, AsmJSParser& parser, ParseNode* stmtList, bool* validated) { *validated = false; // 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); } bool js::CompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms, AsmJSParser& parser, ParseNode* stmtList, bool* validated) { return DoCompileAsmJS(fc, parserAtoms, parser, stmtList, validated); } bool js::CompileAsmJS(FrontendContext* fc, ParserAtomsTable& parserAtoms, AsmJSParser& parser, ParseNode* stmtList, bool* validated) { return DoCompileAsmJS(fc, parserAtoms, parser, stmtList, validated); } /*****************************************************************************/ // asm.js testing functions bool js::IsAsmJSModuleNative(Native native) { return native == InstantiateAsmJS; } bool js::IsAsmJSModule(JSFunction* fun) { return fun->maybeNative() == InstantiateAsmJS; } bool js::IsAsmJSFunction(JSFunction* fun) { return fun->kind() == FunctionFlags::AsmJS; } 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) { return cx->options().asmJS() && IsAsmJSCompilerAvailable(cx); } bool js::IsAsmJSCompilationAvailable(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); bool available = IsAsmJSCompilationAvailable(cx); args.rval().set(BooleanValue(available)); return true; } static JSFunction* MaybeWrappedNativeFunction(const Value& v) { if (!v.isObject()) { return nullptr; } return v.toObject().maybeUnwrapIf(); } 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; } /*****************************************************************************/ // asm.js toString/toSource support 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.maybeScriptSource(); JSStringBuilder out(cx); if (isToSource && fun->isLambda() && !out.append("(")) { return nullptr; } bool haveSource; if (!ScriptSource::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 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.maybeScriptSource(); JSStringBuilder out(cx); if (!out.append("function ")) { return nullptr; } bool haveSource; if (!ScriptSource::loadSource(cx, source, &haveSource)) { return nullptr; } if (!haveSource) { // asm.js functions can't be anonymous MOZ_ASSERT(fun->explicitName()); if (!out.append(fun->explicitName())) { return nullptr; } if (!out.append("() {\n [native code]\n}")) { return nullptr; } } else { Rooted src(cx, source->substring(cx, begin, end)); if (!src) { return nullptr; } if (!out.append(src)) { return nullptr; } } return out.finishString(); } bool js::IsValidAsmJSHeapLength(size_t length) { if (length < MinHeapLength) { return false; } // The heap length is limited by what a wasm memory32 can handle. if (length > MaxMemoryBytes(IndexType::I32)) { return false; } return wasm::IsValidARMImmediate(length); }