/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * 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 "jsmath.h" #include "jsprf.h" #include "jsstr.h" #include "jsutil.h" #include "jswrapper.h" #include "builtin/SIMD.h" #include "frontend/Parser.h" #include "gc/Policy.h" #include "js/MemoryMetrics.h" #include "vm/SelfHosting.h" #include "vm/StringBuffer.h" #include "vm/Time.h" #include "vm/TypedArrayObject.h" #include "wasm/WasmBinaryFormat.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmInstance.h" #include "wasm/WasmJS.h" #include "wasm/WasmSerialize.h" #include "jsobjinlines.h" #include "frontend/ParseNode-inl.h" #include "vm/ArrayBufferObject-inl.h" using namespace js; using namespace js::frontend; using namespace js::jit; using namespace js::wasm; using mozilla::CeilingLog2; using mozilla::Compression::LZ4; using mozilla::HashGeneric; using mozilla::IsNaN; using mozilla::IsNegativeZero; using mozilla::IsPowerOfTwo; using mozilla::Maybe; using mozilla::Move; using mozilla::PodCopy; using mozilla::PodEqual; using mozilla::PodZero; using mozilla::PositiveInfinity; using JS::AsmJSOption; using JS::GenericNaN; /*****************************************************************************/ // 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; static uint32_t RoundUpToNextValidAsmJSHeapLength(uint32_t length) { if (length <= MinHeapLength) return MinHeapLength; return wasm::RoundUpToNextValidARMImmediate(length); } /*****************************************************************************/ // 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 }; // The asm.js spec will recognize this set of builtin Atomics functions. enum AsmJSAtomicsBuiltinFunction { AsmJSAtomicsBuiltin_compareExchange, AsmJSAtomicsBuiltin_exchange, AsmJSAtomicsBuiltin_load, AsmJSAtomicsBuiltin_store, AsmJSAtomicsBuiltin_add, AsmJSAtomicsBuiltin_sub, AsmJSAtomicsBuiltin_and, AsmJSAtomicsBuiltin_or, AsmJSAtomicsBuiltin_xor, AsmJSAtomicsBuiltin_isLockFree }; // An AsmJSGlobal represents a JS global variable in the asm.js module function. class AsmJSGlobal { public: enum Which { Variable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, AtomicsBuiltinFunction, Constant, SimdCtor, SimdOp }; enum VarInitKind { InitConstant, InitImport }; enum ConstantKind { GlobalConstant, MathConstant }; private: struct CacheablePod { Which which_; union V { struct { VarInitKind initKind_; union U { ValType importType_; Val val_; U() {} } u; } var; uint32_t ffiIndex_; Scalar::Type viewType_; AsmJSMathBuiltinFunction mathBuiltinFunc_; AsmJSAtomicsBuiltinFunction atomicsBuiltinFunc_; SimdType simdCtorType_; struct { SimdType type_; SimdOperation which_; } simdOp; struct { ConstantKind kind_; double value_; } constant; V() {} } u; } pod; CacheableChars field_; friend class ModuleValidator; public: AsmJSGlobal() = default; AsmJSGlobal(Which which, UniqueChars field) { mozilla::PodZero(&pod); // zero padding for Valgrind pod.which_ = which; field_ = 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_; } Val 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 pod.u.var.u.importType_; } 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_; } AsmJSAtomicsBuiltinFunction atomicsBuiltinFunction() const { MOZ_ASSERT(pod.which_ == AtomicsBuiltinFunction); return pod.u.atomicsBuiltinFunc_; } SimdType simdCtorType() const { MOZ_ASSERT(pod.which_ == SimdCtor); return pod.u.simdCtorType_; } SimdOperation simdOperation() const { MOZ_ASSERT(pod.which_ == SimdOp); return pod.u.simdOp.which_; } SimdType simdOperationType() const { MOZ_ASSERT(pod.which_ == SimdOp); return pod.u.simdOp.type_; } 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_; } WASM_DECLARE_SERIALIZABLE(AsmJSGlobal); }; typedef Vector AsmJSGlobalVector; // 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_; } }; typedef Vector AsmJSImportVector; // 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_; } }; typedef Vector AsmJSExportVector; enum class CacheResult { Hit, Miss }; // 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; bool usesSimd = false; AsmJSMetadataCacheablePod() = default; }; struct js::AsmJSMetadata : Metadata, AsmJSMetadataCacheablePod { AsmJSGlobalVector asmJSGlobals; AsmJSImportVector asmJSImports; AsmJSExportVector asmJSExports; CacheableCharsVector asmJSFuncNames; CacheableChars globalArgumentName; CacheableChars importArgumentName; CacheableChars bufferArgumentName; CacheResult cacheResult; // 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; uint32_t srcBodyStart; bool strict; ScriptSourceHolder scriptSource; uint32_t srcEndBeforeCurly() const { return srcStart + srcLength; } uint32_t srcEndAfterCurly() const { return srcStart + srcLengthWithRightBrace; } AsmJSMetadata() : Metadata(ModuleKind::AsmJS), cacheResult(CacheResult::Miss), srcStart(0), srcBodyStart(0), strict(false) {} ~AsmJSMetadata() override {} 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 scriptSource.get()->mutedErrors(); } const char16_t* displayURL() const override { return scriptSource.get()->hasDisplayURL() ? scriptSource.get()->displayURL() : nullptr; } ScriptSource* maybeScriptSource() const override { return scriptSource.get(); } bool getFuncName(JSContext* cx, const Bytes*, uint32_t funcIndex, TwoByteName* name) const override { // asm.js doesn't allow exporting imports or putting imports in tables MOZ_ASSERT(funcIndex >= AsmJSFirstDefFuncIndex); const char* p = asmJSFuncNames[funcIndex - AsmJSFirstDefFuncIndex].get(); UTF8Chars utf8(p, strlen(p)); size_t twoByteLength; UniqueTwoByteChars chars(JS::UTF8CharsToNewTwoByteCharsZ(cx, utf8, &twoByteLength).get()); if (!chars) return false; if (!name->growByUninitialized(twoByteLength)) return false; PodCopy(name->begin(), chars.get(), twoByteLength); return true; } AsmJSMetadataCacheablePod& pod() { return *this; } const AsmJSMetadataCacheablePod& pod() const { return *this; } WASM_DECLARE_SERIALIZABLE_OVERRIDE(AsmJSMetadata) }; typedef RefPtr MutableAsmJSMetadata; /*****************************************************************************/ // ParseNode utilities static inline ParseNode* NextNode(ParseNode* pn) { return pn->pn_next; } static inline ParseNode* UnaryKid(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_UNARY)); return pn->pn_kid; } static inline ParseNode* BinaryRight(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_BINARY)); return pn->pn_right; } static inline ParseNode* BinaryLeft(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_BINARY)); return pn->pn_left; } static inline ParseNode* ReturnExpr(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_RETURN)); return UnaryKid(pn); } static inline ParseNode* TernaryKid1(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_TERNARY)); return pn->pn_kid1; } static inline ParseNode* TernaryKid2(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_TERNARY)); return pn->pn_kid2; } static inline ParseNode* TernaryKid3(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_TERNARY)); return pn->pn_kid3; } static inline ParseNode* ListHead(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); return pn->pn_head; } static inline unsigned ListLength(ParseNode* pn) { MOZ_ASSERT(pn->isArity(PN_LIST)); return pn->pn_count; } static inline ParseNode* CallCallee(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_CALL)); return ListHead(pn); } static inline unsigned CallArgListLength(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_CALL)); MOZ_ASSERT(ListLength(pn) >= 1); return ListLength(pn) - 1; } static inline ParseNode* CallArgList(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_CALL)); return NextNode(ListHead(pn)); } static inline ParseNode* VarListHead(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)); 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->isArity(PN_LIST)); MOZ_ASSERT(pn->pn_count == 2); return ListHead(pn); } static inline ParseNode* BinaryOpRight(ParseNode* pn) { MOZ_ASSERT(pn->isBinaryOperation()); MOZ_ASSERT(pn->isArity(PN_LIST)); MOZ_ASSERT(pn->pn_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(PNK_STAR)); return BinaryOpLeft(pn); } static inline ParseNode* MultiplyRight(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_STAR)); return BinaryOpRight(pn); } static inline ParseNode* AddSubLeft(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_ADD) || pn->isKind(PNK_SUB)); return BinaryOpLeft(pn); } static inline ParseNode* AddSubRight(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_ADD) || pn->isKind(PNK_SUB)); return BinaryOpRight(pn); } static inline ParseNode* DivOrModLeft(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_DIV) || pn->isKind(PNK_MOD)); return BinaryOpLeft(pn); } static inline ParseNode* DivOrModRight(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_DIV) || pn->isKind(PNK_MOD)); 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(PNK_SEMI); } static inline ParseNode* ExpressionStatementExpr(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_SEMI)); return UnaryKid(pn); } static inline PropertyName* LoopControlMaybeLabel(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_BREAK) || pn->isKind(PNK_CONTINUE)); MOZ_ASSERT(pn->isArity(PN_NULLARY)); return pn->as().label(); } static inline PropertyName* LabeledStatementLabel(ParseNode* pn) { return pn->as().label(); } static inline ParseNode* LabeledStatementStatement(ParseNode* pn) { return pn->as().statement(); } static double NumberNodeValue(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_NUMBER)); return pn->pn_dval; } static bool NumberNodeHasFrac(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_NUMBER)); return pn->pn_u.number.decimalPoint == HasDecimal; } static ParseNode* DotBase(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_DOT)); MOZ_ASSERT(pn->isArity(PN_NAME)); return pn->expr(); } static PropertyName* DotMember(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_DOT)); MOZ_ASSERT(pn->isArity(PN_NAME)); return pn->pn_atom->asPropertyName(); } static ParseNode* ElemBase(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_ELEM)); return BinaryLeft(pn); } static ParseNode* ElemIndex(ParseNode* pn) { MOZ_ASSERT(pn->isKind(PNK_ELEM)); return BinaryRight(pn); } static inline JSFunction* FunctionObject(ParseNode* fn) { MOZ_ASSERT(fn->isKind(PNK_FUNCTION)); MOZ_ASSERT(fn->isArity(PN_CODE)); return fn->pn_funbox->function(); } static inline PropertyName* FunctionName(ParseNode* fn) { if (JSAtom* name = FunctionObject(fn)->explicitName()) return name->asPropertyName(); return nullptr; } static inline ParseNode* FunctionStatementList(ParseNode* fn) { MOZ_ASSERT(fn->pn_body->isKind(PNK_PARAMSBODY)); ParseNode* last = fn->pn_body->last(); MOZ_ASSERT(last->isKind(PNK_LEXICALSCOPE)); MOZ_ASSERT(last->isEmptyScope()); ParseNode* body = last->scopeBody(); MOZ_ASSERT(body->isKind(PNK_STATEMENTLIST)); return body; } static inline bool IsNormalObjectField(ExclusiveContext* cx, ParseNode* pn) { return pn->isKind(PNK_COLON) && pn->getOp() == JSOP_INITPROP && BinaryLeft(pn)->isKind(PNK_OBJECT_PROPERTY_NAME); } static inline PropertyName* ObjectNormalFieldName(ExclusiveContext* cx, ParseNode* pn) { MOZ_ASSERT(IsNormalObjectField(cx, pn)); MOZ_ASSERT(BinaryLeft(pn)->isKind(PNK_OBJECT_PROPERTY_NAME)); return BinaryLeft(pn)->pn_atom->asPropertyName(); } static inline ParseNode* ObjectNormalFieldInitializer(ExclusiveContext* cx, ParseNode* pn) { MOZ_ASSERT(IsNormalObjectField(cx, pn)); return BinaryRight(pn); } static inline ParseNode* MaybeInitializer(ParseNode* pn) { return pn->expr(); } static inline bool IsUseOfName(ParseNode* pn, PropertyName* name) { return pn->isKind(PNK_NAME) && pn->name() == name; } static inline bool IsIgnoredDirectiveName(ExclusiveContext* cx, JSAtom* atom) { return atom != cx->names().useStrict; } static inline bool IsIgnoredDirective(ExclusiveContext* cx, ParseNode* pn) { return pn->isKind(PNK_SEMI) && UnaryKid(pn) && UnaryKid(pn)->isKind(PNK_STRING) && IsIgnoredDirectiveName(cx, UnaryKid(pn)->pn_atom); } static inline bool IsEmptyStatement(ParseNode* pn) { return pn->isKind(PNK_SEMI) && !UnaryKid(pn); } 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); } static bool GetToken(AsmJSParser& parser, TokenKind* tkp) { TokenStream& ts = parser.tokenStream; TokenKind tk; while (true) { if (!ts.getToken(&tk, TokenStream::Operand)) return false; if (tk != TOK_SEMI) break; } *tkp = tk; return true; } static bool PeekToken(AsmJSParser& parser, TokenKind* tkp) { TokenStream& ts = parser.tokenStream; TokenKind tk; while (true) { if (!ts.peekToken(&tk, TokenStream::Operand)) return false; if (tk != TOK_SEMI) break; ts.consumeKnownToken(TOK_SEMI, TokenStream::Operand); } *tkp = tk; return true; } static bool ParseVarOrConstStatement(AsmJSParser& parser, ParseNode** var) { TokenKind tk; if (!PeekToken(parser, &tk)) return false; if (tk != TOK_VAR && tk != TOK_CONST) { *var = nullptr; return true; } *var = parser.statementListItem(YieldIsName); if (!*var) return false; MOZ_ASSERT((*var)->isKind(PNK_VAR) || (*var)->isKind(PNK_CONST)); 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. // // This class distinguishes between signed and unsigned integer SIMD types like // Int32x4 and Uint32x4, and so does Type below. The wasm ValType and ExprType // enums, and the wasm::Val class do not. class NumLit { public: enum Which { Fixnum, NegativeInt, BigUnsigned, Double, Float, Int8x16, Int16x8, Int32x4, Uint8x16, Uint16x8, Uint32x4, Float32x4, Bool8x16, Bool16x8, Bool32x4, OutOfRangeInt = -1 }; private: Which which_; union { JS::UninitializedValue scalar_; SimdConstant simd_; } u; public: NumLit() = default; NumLit(Which w, const Value& v) : which_(w) { u.scalar_ = v; MOZ_ASSERT(!isSimd()); } NumLit(Which w, SimdConstant c) : which_(w) { u.simd_ = c; MOZ_ASSERT(isSimd()); } Which which() const { return which_; } int32_t toInt32() const { MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt || which_ == BigUnsigned); return u.scalar_.asValueRef().toInt32(); } uint32_t toUint32() const { return (uint32_t)toInt32(); } RawF64 toDouble() const { MOZ_ASSERT(which_ == Double); return RawF64(u.scalar_.asValueRef().toDouble()); } RawF32 toFloat() const { MOZ_ASSERT(which_ == Float); return RawF32(float(u.scalar_.asValueRef().toDouble())); } Value scalarValue() const { MOZ_ASSERT(which_ != OutOfRangeInt); return u.scalar_.asValueRef(); } bool isSimd() const { return which_ == Int8x16 || which_ == Uint8x16 || which_ == Int16x8 || which_ == Uint16x8 || which_ == Int32x4 || which_ == Uint32x4 || which_ == Float32x4 || which_ == Bool8x16 || which_ == Bool16x8 || which_ == Bool32x4; } const SimdConstant& simdValue() const { MOZ_ASSERT(isSimd()); return u.simd_; } 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 toDouble().bits() == 0; case NumLit::Float: return toFloat().bits() == 0; case NumLit::Int8x16: case NumLit::Uint8x16: case NumLit::Bool8x16: return simdValue() == SimdConstant::SplatX16(0); case NumLit::Int16x8: case NumLit::Uint16x8: case NumLit::Bool16x8: return simdValue() == SimdConstant::SplatX8(0); case NumLit::Int32x4: case NumLit::Uint32x4: case NumLit::Bool32x4: return simdValue() == SimdConstant::SplatX4(0); case NumLit::Float32x4: return simdValue() == SimdConstant::SplatX4(0.f); case NumLit::OutOfRangeInt: MOZ_CRASH("can't be here because of valid() check above"); } return false; } Val value() const { switch (which_) { case NumLit::Fixnum: case NumLit::NegativeInt: case NumLit::BigUnsigned: return Val(toUint32()); case NumLit::Float: return Val(toFloat()); case NumLit::Double: return Val(toDouble()); case NumLit::Int8x16: case NumLit::Uint8x16: return Val(simdValue().asInt8x16()); case NumLit::Int16x8: case NumLit::Uint16x8: return Val(simdValue().asInt16x8()); case NumLit::Int32x4: case NumLit::Uint32x4: return Val(simdValue().asInt32x4()); case NumLit::Float32x4: return Val(simdValue().asFloat32x4()); case NumLit::Bool8x16: return Val(simdValue().asInt8x16(), ValType::B8x16); case NumLit::Bool16x8: return Val(simdValue().asInt16x8(), ValType::B16x8); case NumLit::Bool32x4: return Val(simdValue().asInt32x4(), ValType::B32x4); 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, and the SIMD types. This is almost equivalent to wasm::ValType, // except the integer SIMD types have signed/unsigned variants. // // Void is also part of the canonical subset which then maps to wasm::ExprType. // // Note that while the canonical subset distinguishes signed and unsigned SIMD // types, it only uses |Int| to represent signed and unsigned 32-bit integers. // This is because the scalar coersions x|0 and x>>>0 work with any kind of // integer input, while the SIMD check functions throw a TypeError if the passed // type doesn't match. // class Type { public: enum Which { Fixnum = NumLit::Fixnum, Signed = NumLit::NegativeInt, Unsigned = NumLit::BigUnsigned, DoubleLit = NumLit::Double, Float = NumLit::Float, Int8x16 = NumLit::Int8x16, Int16x8 = NumLit::Int16x8, Int32x4 = NumLit::Int32x4, Uint8x16 = NumLit::Uint8x16, Uint16x8 = NumLit::Uint16x8, Uint32x4 = NumLit::Uint32x4, Float32x4 = NumLit::Float32x4, Bool8x16 = NumLit::Bool8x16, Bool16x8 = NumLit::Bool16x8, Bool32x4 = NumLit::Bool32x4, Double, MaybeDouble, MaybeFloat, Floatish, Int, Intish, Void }; private: Which which_; public: Type() = default; MOZ_IMPLICIT Type(Which w) : which_(w) {} MOZ_IMPLICIT Type(SimdType type) { switch (type) { case SimdType::Int8x16: which_ = Int8x16; return; case SimdType::Int16x8: which_ = Int16x8; return; case SimdType::Int32x4: which_ = Int32x4; return; case SimdType::Uint8x16: which_ = Uint8x16; return; case SimdType::Uint16x8: which_ = Uint16x8; return; case SimdType::Uint32x4: which_ = Uint32x4; return; case SimdType::Float32x4: which_ = Float32x4; return; case SimdType::Bool8x16: which_ = Bool8x16; return; case SimdType::Bool16x8: which_ = Bool16x8; return; case SimdType::Bool32x4: which_ = Bool32x4; return; default: break; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("bad SimdType"); } // 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 <= Bool32x4); Type t; t.which_ = which; return t; } // Map |t| to one of the canonical vartype representations of a // wasm::ExprType. 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 Int8x16: case Int16x8: case Int32x4: case Uint8x16: case Uint16x8: case Uint32x4: case Float32x4: case Bool8x16: case Bool16x8: case Bool32x4: return t; case MaybeDouble: case MaybeFloat: case Floatish: case Intish: // These types need some kind of coercion, they can't be mapped // to an ExprType. 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 Int8x16: return isInt8x16(); case Int16x8: return isInt16x8(); case Int32x4: return isInt32x4(); case Uint8x16: return isUint8x16(); case Uint16x8: return isUint16x8(); case Uint32x4: return isUint32x4(); case Float32x4: return isFloat32x4(); case Bool8x16: return isBool8x16(); case Bool16x8: return isBool16x8(); case Bool32x4: return isBool32x4(); case MaybeDouble: return isMaybeDouble(); case MaybeFloat: return isMaybeFloat(); case Floatish: return isFloatish(); case Int: return isInt(); case Intish: return isIntish(); case Fixnum: return isFixnum(); case Void: return isVoid(); } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected rhs type"); } bool isFixnum() const { return which_ == Fixnum; } bool isSigned() const { return which_ == Signed || which_ == Fixnum; } bool isUnsigned() const { return which_ == Unsigned || which_ == Fixnum; } bool isInt() const { return isSigned() || isUnsigned() || which_ == Int; } bool isIntish() const { return isInt() || which_ == Intish; } bool isDoubleLit() const { return which_ == DoubleLit; } bool isDouble() const { return isDoubleLit() || which_ == Double; } bool isMaybeDouble() const { return isDouble() || which_ == MaybeDouble; } bool isFloat() const { return which_ == Float; } bool isMaybeFloat() const { return isFloat() || which_ == MaybeFloat; } bool isFloatish() const { return isMaybeFloat() || which_ == Floatish; } bool isVoid() const { return which_ == Void; } bool isExtern() const { return isDouble() || isSigned(); } bool isInt8x16() const { return which_ == Int8x16; } bool isInt16x8() const { return which_ == Int16x8; } bool isInt32x4() const { return which_ == Int32x4; } bool isUint8x16() const { return which_ == Uint8x16; } bool isUint16x8() const { return which_ == Uint16x8; } bool isUint32x4() const { return which_ == Uint32x4; } bool isFloat32x4() const { return which_ == Float32x4; } bool isBool8x16() const { return which_ == Bool8x16; } bool isBool16x8() const { return which_ == Bool16x8; } bool isBool32x4() const { return which_ == Bool32x4; } bool isSimd() const { return isInt8x16() || isInt16x8() || isInt32x4() || isUint8x16() || isUint16x8() || isUint32x4() || isFloat32x4() || isBool8x16() || isBool16x8() || isBool32x4(); } bool isUnsignedSimd() const { return isUint8x16() || isUint16x8() || isUint32x4(); } // Check if this is one of the valid types for a function argument. bool isArgType() const { return isInt() || isFloat() || isDouble() || (isSimd() && !isUnsignedSimd()); } // Check if this is one of the valid types for a function return value. bool isReturnType() const { return isSigned() || isFloat() || isDouble() || (isSimd() && !isUnsignedSimd()) || 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::ExprType. See Type::canonicalize(). bool isCanonical() const { switch (which()) { case Int: case Float: case Double: case Void: return true; default: return isSimd(); } } // Check if this is a canonical representation of a wasm::ValType. bool isCanonicalValType() const { return !isVoid() && isCanonical(); } // Convert this canonical type to a wasm::ExprType. ExprType canonicalToExprType() const { switch (which()) { case Int: return ExprType::I32; case Float: return ExprType::F32; case Double: return ExprType::F64; case Void: return ExprType::Void; case Uint8x16: case Int8x16: return ExprType::I8x16; case Uint16x8: case Int16x8: return ExprType::I16x8; case Uint32x4: case Int32x4: return ExprType::I32x4; case Float32x4: return ExprType::F32x4; case Bool8x16: return ExprType::B8x16; case Bool16x8: return ExprType::B16x8; case Bool32x4: return ExprType::B32x4; default: MOZ_CRASH("Need canonical type"); } } // Convert this canonical type to a wasm::ValType. ValType canonicalToValType() const { return NonVoidToValType(canonicalToExprType()); } // Convert this type to a wasm::ExprType 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. ExprType toWasmBlockSignatureType() const { switch (which()) { case Fixnum: case Signed: case Unsigned: case Int: case Intish: return ExprType::I32; case Float: case MaybeFloat: case Floatish: return ExprType::F32; case DoubleLit: case Double: case MaybeDouble: return ExprType::F64; case Void: return ExprType::Void; case Uint8x16: case Int8x16: return ExprType::I8x16; case Uint16x8: case Int16x8: return ExprType::I16x8; case Uint32x4: case Int32x4: return ExprType::I32x4; case Float32x4: return ExprType::F32x4; case Bool8x16: return ExprType::B8x16; case Bool16x8: return ExprType::B16x8; case Bool32x4: return ExprType::B32x4; } 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 Int8x16: return "int8x16"; case Int16x8: return "int16x8"; case Int32x4: return "int32x4"; case Uint8x16: return "uint8x16"; case Uint16x8: return "uint16x8"; case Uint32x4: return "uint32x4"; case Float32x4: return "float32x4"; case Bool8x16: return "bool8x16"; case Bool16x8: return "bool16x8"; case Bool32x4: return "bool32x4"; case Void: return "void"; } MOZ_CRASH("Invalid Type"); } }; static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024; // 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. // // Rooting note: ModuleValidator is a stack class that contains unrooted // PropertyName (JSAtom) pointers. This is safe because it cannot be // constructed without a TokenStream reference. TokenStream is itself a stack // class that cannot be constructed without an AutoKeepAtoms being live on the // stack, which prevents collection of atoms. // // ModuleValidator is marked as rooted in the rooting analysis. Don't add // non-JSAtom pointers, or this will break! class MOZ_STACK_CLASS ModuleValidator { public: class Func { PropertyName* name_; uint32_t firstUse_; uint32_t index_; uint32_t srcBegin_; uint32_t srcEnd_; bool defined_; public: Func(PropertyName* name, uint32_t firstUse, uint32_t index) : name_(name), firstUse_(firstUse), index_(index), srcBegin_(0), srcEnd_(0), defined_(false) {} PropertyName* name() const { return name_; } uint32_t firstUse() const { return firstUse_; } bool defined() const { return defined_; } uint32_t index() const { return index_; } void define(ParseNode* fn) { MOZ_ASSERT(!defined_); defined_ = true; srcBegin_ = fn->pn_pos.begin; srcEnd_ = fn->pn_pos.end; } uint32_t srcBegin() const { MOZ_ASSERT(defined_); return srcBegin_; } uint32_t srcEnd() const { MOZ_ASSERT(defined_); return srcEnd_; } }; typedef Vector ConstFuncVector; typedef Vector FuncVector; class FuncPtrTable { uint32_t sigIndex_; PropertyName* name_; uint32_t firstUse_; uint32_t mask_; bool defined_; FuncPtrTable(FuncPtrTable&& rhs) = delete; public: FuncPtrTable(uint32_t sigIndex, PropertyName* name, uint32_t firstUse, uint32_t mask) : sigIndex_(sigIndex), name_(name), firstUse_(firstUse), mask_(mask), defined_(false) {} uint32_t sigIndex() const { return sigIndex_; } PropertyName* name() const { return name_; } uint32_t firstUse() const { return firstUse_; } unsigned mask() const { return mask_; } bool defined() const { return defined_; } void define() { MOZ_ASSERT(!defined_); defined_ = true; } }; typedef Vector FuncPtrTableVector; class Global { public: enum Which { Variable, ConstantLiteral, ConstantImport, Function, FuncPtrTable, FFI, ArrayView, ArrayViewCtor, MathBuiltinFunction, AtomicsBuiltinFunction, SimdCtor, SimdOp }; private: Which which_; union { struct { Type::Which type_; unsigned index_; NumLit literalValue_; } varOrConst; uint32_t funcIndex_; uint32_t funcPtrTableIndex_; uint32_t ffiIndex_; struct { Scalar::Type viewType_; } viewInfo; AsmJSMathBuiltinFunction mathBuiltinFunc_; AsmJSAtomicsBuiltinFunction atomicsBuiltinFunc_; SimdType simdCtorType_; struct { SimdType type_; SimdOperation which_; } simdOp; } u; 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 funcIndex() const { MOZ_ASSERT(which_ == Function); return u.funcIndex_; } uint32_t funcPtrTableIndex() const { MOZ_ASSERT(which_ == FuncPtrTable); return u.funcPtrTableIndex_; } unsigned ffiIndex() const { MOZ_ASSERT(which_ == FFI); return u.ffiIndex_; } bool isAnyArrayView() const { return which_ == ArrayView || which_ == ArrayViewCtor; } Scalar::Type viewType() const { MOZ_ASSERT(isAnyArrayView()); return u.viewInfo.viewType_; } bool isMathFunction() const { return which_ == MathBuiltinFunction; } AsmJSMathBuiltinFunction mathBuiltinFunction() const { MOZ_ASSERT(which_ == MathBuiltinFunction); return u.mathBuiltinFunc_; } bool isAtomicsFunction() const { return which_ == AtomicsBuiltinFunction; } AsmJSAtomicsBuiltinFunction atomicsBuiltinFunction() const { MOZ_ASSERT(which_ == AtomicsBuiltinFunction); return u.atomicsBuiltinFunc_; } bool isSimdCtor() const { return which_ == SimdCtor; } SimdType simdCtorType() const { MOZ_ASSERT(which_ == SimdCtor); return u.simdCtorType_; } bool isSimdOperation() const { return which_ == SimdOp; } SimdOperation simdOperation() const { MOZ_ASSERT(which_ == SimdOp); return u.simdOp.which_; } SimdType simdOperationType() const { MOZ_ASSERT(which_ == SimdOp); return u.simdOp.type_; } }; struct MathBuiltin { enum Kind { Function, Constant }; Kind kind; union { double cst; AsmJSMathBuiltinFunction func; } u; MathBuiltin() : kind(Kind(-1)) {} explicit MathBuiltin(double cst) : kind(Constant) { u.cst = cst; } explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) { u.func = func; } }; struct ArrayView { ArrayView(PropertyName* name, Scalar::Type type) : name(name), type(type) {} PropertyName* name; Scalar::Type type; }; private: class NamedSig { PropertyName* name_; const SigWithId* sig_; public: NamedSig(PropertyName* name, const SigWithId& sig) : name_(name), sig_(&sig) {} PropertyName* name() const { return name_; } const Sig& sig() const { return *sig_; } // Implement HashPolicy: struct Lookup { PropertyName* name; const Sig& sig; Lookup(PropertyName* name, const Sig& sig) : name(name), sig(sig) {} }; static HashNumber hash(Lookup l) { return HashGeneric(l.name, l.sig.hash()); } static bool match(NamedSig lhs, Lookup rhs) { return lhs.name_ == rhs.name && *lhs.sig_ == rhs.sig; } }; typedef HashMap ImportMap; typedef HashMap SigMap; typedef HashMap GlobalMap; typedef HashMap MathNameMap; typedef HashMap AtomicsNameMap; typedef HashMap SimdOperationNameMap; typedef Vector ArrayViewVector; ExclusiveContext* cx_; AsmJSParser& parser_; ParseNode* moduleFunctionNode_; PropertyName* moduleFunctionName_; PropertyName* globalArgumentName_; PropertyName* importArgumentName_; PropertyName* bufferArgumentName_; MathNameMap standardLibraryMathNames_; AtomicsNameMap standardLibraryAtomicsNames_; SimdOperationNameMap standardLibrarySimdOpNames_; RootedFunction dummyFunction_; // Validation-internal state: LifoAlloc validationLifo_; FuncVector functions_; FuncPtrTableVector funcPtrTables_; GlobalMap globalMap_; SigMap sigMap_; ImportMap importMap_; ArrayViewVector arrayViews_; bool atomicsPresent_; bool simdPresent_; // State used to build the AsmJSModule in finish(): ModuleGenerator mg_; MutableAsmJSMetadata asmJSMetadata_; // Error reporting: UniqueChars errorString_; uint32_t errorOffset_; bool errorOverRecursed_; // Helpers: bool addStandardLibraryMathName(const char* name, AsmJSMathBuiltinFunction func) { JSAtom* atom = Atomize(cx_, name, strlen(name)); if (!atom) return false; MathBuiltin builtin(func); return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin); } bool addStandardLibraryMathName(const char* name, double cst) { JSAtom* atom = Atomize(cx_, name, strlen(name)); if (!atom) return false; MathBuiltin builtin(cst); return standardLibraryMathNames_.putNew(atom->asPropertyName(), builtin); } bool addStandardLibraryAtomicsName(const char* name, AsmJSAtomicsBuiltinFunction func) { JSAtom* atom = Atomize(cx_, name, strlen(name)); if (!atom) return false; return standardLibraryAtomicsNames_.putNew(atom->asPropertyName(), func); } bool addStandardLibrarySimdOpName(const char* name, SimdOperation op) { JSAtom* atom = Atomize(cx_, name, strlen(name)); if (!atom) return false; return standardLibrarySimdOpNames_.putNew(atom->asPropertyName(), op); } bool newSig(Sig&& sig, uint32_t* sigIndex) { *sigIndex = 0; if (mg_.numSigs() >= MaxSigs) return failCurrentOffset("too many signatures"); *sigIndex = mg_.numSigs(); mg_.initSig(*sigIndex, Move(sig)); return true; } bool declareSig(Sig&& sig, uint32_t* sigIndex) { SigMap::AddPtr p = sigMap_.lookupForAdd(sig); if (p) { *sigIndex = p->value(); MOZ_ASSERT(mg_.sig(*sigIndex) == sig); return true; } return newSig(Move(sig), sigIndex) && sigMap_.add(p, &mg_.sig(*sigIndex), *sigIndex); } public: ModuleValidator(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* moduleFunctionNode) : cx_(cx), parser_(parser), moduleFunctionNode_(moduleFunctionNode), moduleFunctionName_(FunctionName(moduleFunctionNode)), globalArgumentName_(nullptr), importArgumentName_(nullptr), bufferArgumentName_(nullptr), standardLibraryMathNames_(cx), standardLibraryAtomicsNames_(cx), standardLibrarySimdOpNames_(cx), dummyFunction_(cx), validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE), functions_(cx), funcPtrTables_(cx), globalMap_(cx), sigMap_(cx), importMap_(cx), arrayViews_(cx), atomicsPresent_(false), simdPresent_(false), mg_(ImportVector()), errorString_(nullptr), errorOffset_(UINT32_MAX), errorOverRecursed_(false) {} ~ModuleValidator() { if (errorString_) { MOZ_ASSERT(errorOffset_ != UINT32_MAX); tokenStream().reportAsmJSError(errorOffset_, JSMSG_USE_ASM_TYPE_FAIL, errorString_.get()); } if (errorOverRecursed_) ReportOverRecursed(cx_); } bool init() { asmJSMetadata_ = cx_->new_(); if (!asmJSMetadata_) return false; asmJSMetadata_->toStringStart = moduleFunctionNode_->pn_funbox->toStringStart; asmJSMetadata_->srcStart = moduleFunctionNode_->pn_body->pn_pos.begin; asmJSMetadata_->srcBodyStart = parser_.tokenStream.currentToken().pos.end; asmJSMetadata_->strict = parser_.pc->sc()->strict() && !parser_.pc->sc()->hasExplicitUseStrict(); asmJSMetadata_->scriptSource.reset(parser_.ss); if (!globalMap_.init() || !sigMap_.init() || !importMap_.init()) return false; if (!standardLibraryMathNames_.init() || !addStandardLibraryMathName("sin", AsmJSMathBuiltin_sin) || !addStandardLibraryMathName("cos", AsmJSMathBuiltin_cos) || !addStandardLibraryMathName("tan", AsmJSMathBuiltin_tan) || !addStandardLibraryMathName("asin", AsmJSMathBuiltin_asin) || !addStandardLibraryMathName("acos", AsmJSMathBuiltin_acos) || !addStandardLibraryMathName("atan", AsmJSMathBuiltin_atan) || !addStandardLibraryMathName("ceil", AsmJSMathBuiltin_ceil) || !addStandardLibraryMathName("floor", AsmJSMathBuiltin_floor) || !addStandardLibraryMathName("exp", AsmJSMathBuiltin_exp) || !addStandardLibraryMathName("log", AsmJSMathBuiltin_log) || !addStandardLibraryMathName("pow", AsmJSMathBuiltin_pow) || !addStandardLibraryMathName("sqrt", AsmJSMathBuiltin_sqrt) || !addStandardLibraryMathName("abs", AsmJSMathBuiltin_abs) || !addStandardLibraryMathName("atan2", AsmJSMathBuiltin_atan2) || !addStandardLibraryMathName("imul", AsmJSMathBuiltin_imul) || !addStandardLibraryMathName("clz32", AsmJSMathBuiltin_clz32) || !addStandardLibraryMathName("fround", AsmJSMathBuiltin_fround) || !addStandardLibraryMathName("min", AsmJSMathBuiltin_min) || !addStandardLibraryMathName("max", AsmJSMathBuiltin_max) || !addStandardLibraryMathName("E", M_E) || !addStandardLibraryMathName("LN10", M_LN10) || !addStandardLibraryMathName("LN2", M_LN2) || !addStandardLibraryMathName("LOG2E", M_LOG2E) || !addStandardLibraryMathName("LOG10E", M_LOG10E) || !addStandardLibraryMathName("PI", M_PI) || !addStandardLibraryMathName("SQRT1_2", M_SQRT1_2) || !addStandardLibraryMathName("SQRT2", M_SQRT2)) { return false; } if (!standardLibraryAtomicsNames_.init() || !addStandardLibraryAtomicsName("compareExchange", AsmJSAtomicsBuiltin_compareExchange) || !addStandardLibraryAtomicsName("exchange", AsmJSAtomicsBuiltin_exchange) || !addStandardLibraryAtomicsName("load", AsmJSAtomicsBuiltin_load) || !addStandardLibraryAtomicsName("store", AsmJSAtomicsBuiltin_store) || !addStandardLibraryAtomicsName("add", AsmJSAtomicsBuiltin_add) || !addStandardLibraryAtomicsName("sub", AsmJSAtomicsBuiltin_sub) || !addStandardLibraryAtomicsName("and", AsmJSAtomicsBuiltin_and) || !addStandardLibraryAtomicsName("or", AsmJSAtomicsBuiltin_or) || !addStandardLibraryAtomicsName("xor", AsmJSAtomicsBuiltin_xor) || !addStandardLibraryAtomicsName("isLockFree", AsmJSAtomicsBuiltin_isLockFree)) { return false; } #define ADDSTDLIBSIMDOPNAME(op) || !addStandardLibrarySimdOpName(#op, SimdOperation::Fn_##op) if (!standardLibrarySimdOpNames_.init() FORALL_SIMD_ASMJS_OP(ADDSTDLIBSIMDOPNAME)) { return false; } #undef ADDSTDLIBSIMDOPNAME // This flows into FunctionBox, so must be tenured. dummyFunction_ = NewScriptedFunction(cx_, 0, JSFunction::INTERPRETED, nullptr, /* proto = */ nullptr, gc::AllocKind::FUNCTION, TenuredObject); if (!dummyFunction_) return false; ScriptedCaller scriptedCaller; if (parser_.ss->filename()) { scriptedCaller.line = scriptedCaller.column = 0; // unused scriptedCaller.filename = DuplicateString(parser_.ss->filename()); if (!scriptedCaller.filename) return false; } CompileArgs args; if (!args.initFromContext(cx_, Move(scriptedCaller))) return false; auto genData = MakeUnique(ModuleKind::AsmJS); if (!genData || !genData->sigs.resize(MaxSigs) || !genData->funcSigs.resize(MaxFuncs) || !genData->funcImportGlobalDataOffsets.resize(AsmJSMaxImports) || !genData->tables.resize(MaxTables) || !genData->asmJSSigToTableIndex.resize(MaxSigs)) { return false; } genData->minMemoryLength = RoundUpToNextValidAsmJSHeapLength(0); if (!mg_.init(Move(genData), args, asmJSMetadata_.get())) return false; return true; } ExclusiveContext* cx() const { return cx_; } PropertyName* moduleFunctionName() const { return moduleFunctionName_; } PropertyName* globalArgumentName() const { return globalArgumentName_; } PropertyName* importArgumentName() const { return importArgumentName_; } PropertyName* bufferArgumentName() const { return bufferArgumentName_; } ModuleGenerator& mg() { return mg_; } AsmJSParser& parser() const { return parser_; } TokenStream& tokenStream() const { return parser_.tokenStream; } RootedFunction& dummyFunction() { return dummyFunction_; } bool supportsSimd() const { return cx_->jitSupportsSimd(); } bool atomicsPresent() const { return atomicsPresent_; } uint32_t minMemoryLength() const { return mg_.minMemoryLength(); } void initModuleFunctionName(PropertyName* name) { MOZ_ASSERT(!moduleFunctionName_); moduleFunctionName_ = name; } MOZ_MUST_USE bool initGlobalArgumentName(PropertyName* n) { MOZ_ASSERT(n->isTenured()); globalArgumentName_ = n; if (n) { asmJSMetadata_->globalArgumentName = StringToNewUTF8CharsZ(cx_, *n); if (!asmJSMetadata_->globalArgumentName) return false; } return true; } MOZ_MUST_USE bool initImportArgumentName(PropertyName* n) { MOZ_ASSERT(n->isTenured()); importArgumentName_ = n; if (n) { asmJSMetadata_->importArgumentName = StringToNewUTF8CharsZ(cx_, *n); if (!asmJSMetadata_->importArgumentName) return false; } return true; } MOZ_MUST_USE bool initBufferArgumentName(PropertyName* n) { MOZ_ASSERT(n->isTenured()); bufferArgumentName_ = n; if (n) { asmJSMetadata_->bufferArgumentName = StringToNewUTF8CharsZ(cx_, *n); if (!asmJSMetadata_->bufferArgumentName) return false; } return true; } bool addGlobalVarInit(PropertyName* var, const NumLit& lit, Type type, bool isConst) { MOZ_ASSERT(type.isGlobalVarType()); MOZ_ASSERT(type == Type::canonicalize(Type::lit(lit))); uint32_t index; if (!mg_.addGlobal(type.canonicalToValType(), isConst, &index)) return false; Global::Which which = isConst ? Global::ConstantLiteral : Global::Variable; Global* global = validationLifo_.new_(which); if (!global) return false; global->u.varOrConst.index_ = index; global->u.varOrConst.type_ = (isConst ? Type::lit(lit) : type).which(); if (isConst) global->u.varOrConst.literalValue_ = lit; 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(Move(g)); } bool addGlobalVarImport(PropertyName* var, PropertyName* field, Type type, bool isConst) { MOZ_ASSERT(type.isGlobalVarType()); UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; uint32_t index; ValType valType = type.canonicalToValType(); if (!mg_.addGlobal(valType, isConst, &index)) return false; Global::Which which = isConst ? Global::ConstantImport : Global::Variable; Global* global = validationLifo_.new_(which); if (!global) return false; global->u.varOrConst.index_ = index; global->u.varOrConst.type_ = type.which(); if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::Variable, Move(fieldChars)); g.pod.u.var.initKind_ = AsmJSGlobal::InitImport; g.pod.u.var.u.importType_ = valType; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addArrayView(PropertyName* var, Scalar::Type vt, PropertyName* maybeField) { UniqueChars fieldChars; if (maybeField) { fieldChars = StringToNewUTF8CharsZ(cx_, *maybeField); if (!fieldChars) return false; } if (!arrayViews_.append(ArrayView(var, vt))) return false; Global* global = validationLifo_.new_(Global::ArrayView); if (!global) return false; global->u.viewInfo.viewType_ = vt; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::ArrayView, Move(fieldChars)); g.pod.u.viewType_ = vt; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addMathBuiltinFunction(PropertyName* var, AsmJSMathBuiltinFunction func, PropertyName* field) { UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; Global* global = validationLifo_.new_(Global::MathBuiltinFunction); if (!global) return false; global->u.mathBuiltinFunc_ = func; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::MathBuiltinFunction, Move(fieldChars)); g.pod.u.mathBuiltinFunc_ = func; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } private: bool addGlobalDoubleConstant(PropertyName* var, double constant) { Global* global = validationLifo_.new_(Global::ConstantLiteral); if (!global) return false; global->u.varOrConst.type_ = Type::Double; global->u.varOrConst.literalValue_ = NumLit(NumLit::Double, DoubleValue(constant)); return globalMap_.putNew(var, global); } public: bool addMathBuiltinConstant(PropertyName* var, double constant, PropertyName* field) { UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; if (!addGlobalDoubleConstant(var, constant)) return false; AsmJSGlobal g(AsmJSGlobal::Constant, Move(fieldChars)); g.pod.u.constant.value_ = constant; g.pod.u.constant.kind_ = AsmJSGlobal::MathConstant; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addGlobalConstant(PropertyName* var, double constant, PropertyName* field) { UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; if (!addGlobalDoubleConstant(var, constant)) return false; AsmJSGlobal g(AsmJSGlobal::Constant, Move(fieldChars)); g.pod.u.constant.value_ = constant; g.pod.u.constant.kind_ = AsmJSGlobal::GlobalConstant; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addAtomicsBuiltinFunction(PropertyName* var, AsmJSAtomicsBuiltinFunction func, PropertyName* field) { if (!JitOptions.asmJSAtomicsEnable) return failCurrentOffset("asm.js Atomics only enabled in wasm test mode"); atomicsPresent_ = true; UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; Global* global = validationLifo_.new_(Global::AtomicsBuiltinFunction); if (!global) return false; global->u.atomicsBuiltinFunc_ = func; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::AtomicsBuiltinFunction, Move(fieldChars)); g.pod.u.atomicsBuiltinFunc_ = func; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addSimdCtor(PropertyName* var, SimdType type, PropertyName* field) { simdPresent_ = true; UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; Global* global = validationLifo_.new_(Global::SimdCtor); if (!global) return false; global->u.simdCtorType_ = type; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::SimdCtor, Move(fieldChars)); g.pod.u.simdCtorType_ = type; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addSimdOperation(PropertyName* var, SimdType type, SimdOperation op, PropertyName* field) { simdPresent_ = true; UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; Global* global = validationLifo_.new_(Global::SimdOp); if (!global) return false; global->u.simdOp.type_ = type; global->u.simdOp.which_ = op; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::SimdOp, Move(fieldChars)); g.pod.u.simdOp.type_ = type; g.pod.u.simdOp.which_ = op; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addArrayViewCtor(PropertyName* var, Scalar::Type vt, PropertyName* field) { UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; Global* global = validationLifo_.new_(Global::ArrayViewCtor); if (!global) return false; global->u.viewInfo.viewType_ = vt; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::ArrayViewCtor, Move(fieldChars)); g.pod.u.viewType_ = vt; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addFFI(PropertyName* var, PropertyName* field) { UniqueChars fieldChars = StringToNewUTF8CharsZ(cx_, *field); if (!fieldChars) return false; if (asmJSMetadata_->numFFIs == UINT32_MAX) return false; uint32_t ffiIndex = asmJSMetadata_->numFFIs++; Global* global = validationLifo_.new_(Global::FFI); if (!global) return false; global->u.ffiIndex_ = ffiIndex; if (!globalMap_.putNew(var, global)) return false; AsmJSGlobal g(AsmJSGlobal::FFI, Move(fieldChars)); g.pod.u.ffiIndex_ = ffiIndex; return asmJSMetadata_->asmJSGlobals.append(Move(g)); } bool addExportField(ParseNode* pn, const Func& func, PropertyName* maybeField) { // Record the field name of this export. CacheableChars fieldChars; if (maybeField) fieldChars = StringToNewUTF8CharsZ(cx_, *maybeField); else fieldChars = DuplicateString(""); if (!fieldChars) return false; // Declare which function is exported which gives us an index into the // module FuncExportVector. if (!mg_.addFuncExport(Move(fieldChars), func.index())) 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(func.index(), func.srcBegin() - asmJSMetadata_->srcStart, func.srcEnd() - asmJSMetadata_->srcStart); } bool addFunction(PropertyName* name, uint32_t firstUse, Sig&& sig, Func** func) { uint32_t sigIndex; if (!declareSig(Move(sig), &sigIndex)) return false; uint32_t funcIndex = AsmJSFirstDefFuncIndex + numFunctions(); if (funcIndex >= MaxFuncs) return failCurrentOffset("too many functions"); mg_.initFuncSig(funcIndex, sigIndex); Global* global = validationLifo_.new_(Global::Function); if (!global) return false; global->u.funcIndex_ = funcIndex; if (!globalMap_.putNew(name, global)) return false; *func = validationLifo_.new_(name, firstUse, funcIndex); return *func && functions_.append(*func); } bool declareFuncPtrTable(Sig&& sig, PropertyName* name, uint32_t firstUse, uint32_t mask, uint32_t* index) { if (mask > MaxTableElems) return failCurrentOffset("function pointer table too big"); uint32_t sigIndex; if (!newSig(Move(sig), &sigIndex)) return false; if (!mg_.initSigTableLength(sigIndex, mask + 1)) return false; Global* global = validationLifo_.new_(Global::FuncPtrTable); if (!global) return false; global->u.funcPtrTableIndex_ = *index = funcPtrTables_.length(); if (!globalMap_.putNew(name, global)) return false; FuncPtrTable* t = validationLifo_.new_(sigIndex, name, firstUse, mask); return t && funcPtrTables_.append(t); } bool defineFuncPtrTable(uint32_t funcPtrTableIndex, Uint32Vector&& elems) { FuncPtrTable& table = *funcPtrTables_[funcPtrTableIndex]; if (table.defined()) return false; table.define(); return mg_.initSigTableElems(table.sigIndex(), Move(elems)); } bool declareImport(PropertyName* name, Sig&& sig, unsigned ffiIndex, uint32_t* funcIndex) { ImportMap::AddPtr p = importMap_.lookupForAdd(NamedSig::Lookup(name, sig)); if (p) { *funcIndex = p->value(); return true; } *funcIndex = asmJSMetadata_->asmJSImports.length(); if (*funcIndex > AsmJSMaxImports) return failCurrentOffset("too many imports"); if (!asmJSMetadata_->asmJSImports.emplaceBack(ffiIndex)) return false; uint32_t sigIndex; if (!declareSig(Move(sig), &sigIndex)) return false; if (!mg_.initImport(*funcIndex, sigIndex)) return false; return importMap_.add(p, NamedSig(name, mg_.sig(sigIndex)), *funcIndex); } 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 > mg_.minMemoryLength()) mg_.bumpMinMemoryLength(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 failCurrentOffset(const char* str) { return failOffset(tokenStream().currentToken().pos.begin, str); } 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_ASSERT(!hasAlreadyFailed()); MOZ_ASSERT(errorOffset_ == UINT32_MAX); MOZ_ASSERT(fmt); errorOffset_ = offset; errorString_.reset(JS_vsmprintf(fmt, ap)); return false; } bool failfOffset(uint32_t offset, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) { va_list ap; va_start(ap, fmt); failfVAOffset(offset, fmt, ap); va_end(ap); return false; } bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) { va_list ap; va_start(ap, fmt); failfVAOffset(pn->pn_pos.begin, fmt, ap); va_end(ap); return false; } bool failNameOffset(uint32_t offset, const char* fmt, PropertyName* name) { // This function is invoked without the caller properly rooting its locals. gc::AutoSuppressGC suppress(cx_); JSAutoByteString bytes; if (AtomToPrintableString(cx_, name, &bytes)) failfOffset(offset, fmt, bytes.ptr()); return false; } bool failName(ParseNode* pn, const char* fmt, PropertyName* name) { return failNameOffset(pn->pn_pos.begin, fmt, name); } bool failOverRecursed() { errorOverRecursed_ = true; return false; } unsigned numArrayViews() const { return arrayViews_.length(); } const ArrayView& arrayView(unsigned i) const { return arrayViews_[i]; } unsigned numFunctions() const { return functions_.length(); } Func& function(unsigned i) const { return *functions_[i]; } unsigned numFuncPtrTables() const { return funcPtrTables_.length(); } FuncPtrTable& funcPtrTable(unsigned i) const { return *funcPtrTables_[i]; } const Global* lookupGlobal(PropertyName* name) const { if (GlobalMap::Ptr p = globalMap_.lookup(name)) return p->value(); return nullptr; } Func* lookupFunction(PropertyName* name) { if (GlobalMap::Ptr p = globalMap_.lookup(name)) { Global* value = p->value(); if (value->which() == Global::Function) { MOZ_ASSERT(value->funcIndex() >= AsmJSFirstDefFuncIndex); return functions_[value->funcIndex() - AsmJSFirstDefFuncIndex]; } } return nullptr; } bool lookupStandardLibraryMathName(PropertyName* name, MathBuiltin* mathBuiltin) const { if (MathNameMap::Ptr p = standardLibraryMathNames_.lookup(name)) { *mathBuiltin = p->value(); return true; } return false; } bool lookupStandardLibraryAtomicsName(PropertyName* name, AsmJSAtomicsBuiltinFunction* atomicsBuiltin) const { if (AtomicsNameMap::Ptr p = standardLibraryAtomicsNames_.lookup(name)) { *atomicsBuiltin = p->value(); return true; } return false; } bool lookupStandardSimdOpName(PropertyName* name, SimdOperation* op) const { if (SimdOperationNameMap::Ptr p = standardLibrarySimdOpNames_.lookup(name)) { *op = p->value(); return true; } return false; } bool startFunctionBodies() { return mg_.startFuncDefs(); } bool finishFunctionBodies() { return mg_.finishFuncDefs(); } SharedModule finish() { if (!arrayViews_.empty()) mg_.initMemoryUsage(atomicsPresent_ ? MemoryUsage::Shared : MemoryUsage::Unshared); asmJSMetadata_->usesSimd = simdPresent_; MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty()); for (const Func* func : functions_) { CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func->name()); if (!funcName || !asmJSMetadata_->asmJSFuncNames.emplaceBack(Move(funcName))) return nullptr; } uint32_t endBeforeCurly = tokenStream().currentToken().pos.end; asmJSMetadata_->srcLength = endBeforeCurly - asmJSMetadata_->srcStart; TokenPos pos; JS_ALWAYS_TRUE(tokenStream().peekTokenPos(&pos, TokenStream::Operand)); uint32_t endAfterCurly = pos.end; asmJSMetadata_->srcLengthWithRightBrace = endAfterCurly - asmJSMetadata_->srcStart; // asm.js does not have any wasm bytecode to save; view-source is // provided through the ScriptSource. SharedBytes bytes = js_new(); if (!bytes) return nullptr; return mg_.finish(*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(PNK_NUMBER) || (pn->isKind(PNK_NEG) && UnaryKid(pn)->isKind(PNK_NUMBER)); } static bool IsCallToGlobal(ModuleValidator& m, ParseNode* pn, const ModuleValidator::Global** global) { if (!pn->isKind(PNK_CALL)) return false; ParseNode* callee = CallCallee(pn); if (!callee->isKind(PNK_NAME)) return false; *global = m.lookupGlobal(callee->name()); return !!*global; } static bool IsCoercionCall(ModuleValidator& m, ParseNode* pn, Type* coerceTo, ParseNode** coercedExpr) { const ModuleValidator::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; } if (global->isSimdOperation() && global->simdOperation() == SimdOperation::Fn_check) { *coerceTo = global->simdOperationType(); return true; } return false; } static bool IsFloatLiteral(ModuleValidator& 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 IsSimdTuple(ModuleValidator& m, ParseNode* pn, SimdType* type) { const ModuleValidator::Global* global; if (!IsCallToGlobal(m, pn, &global)) return false; if (!global->isSimdCtor()) return false; if (CallArgListLength(pn) != GetSimdLanes(global->simdCtorType())) return false; *type = global->simdCtorType(); return true; } static bool IsNumericLiteral(ModuleValidator& m, ParseNode* pn, bool* isSimd = nullptr); static NumLit ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn); static inline bool IsLiteralInt(ModuleValidator& m, ParseNode* pn, uint32_t* u32); static bool IsSimdLiteral(ModuleValidator& m, ParseNode* pn) { SimdType type; if (!IsSimdTuple(m, pn, &type)) return false; ParseNode* arg = CallArgList(pn); unsigned length = GetSimdLanes(type); for (unsigned i = 0; i < length; i++) { if (!IsNumericLiteral(m, arg)) return false; uint32_t _; switch (type) { case SimdType::Int8x16: case SimdType::Int16x8: case SimdType::Int32x4: case SimdType::Uint8x16: case SimdType::Uint16x8: case SimdType::Uint32x4: case SimdType::Bool8x16: case SimdType::Bool16x8: case SimdType::Bool32x4: if (!IsLiteralInt(m, arg, &_)) return false; break; case SimdType::Float32x4: if (!IsNumericNonFloatLiteral(arg)) return false; break; default: MOZ_CRASH("unhandled simd type"); } arg = NextNode(arg); } MOZ_ASSERT(arg == nullptr); return true; } static bool IsNumericLiteral(ModuleValidator& m, ParseNode* pn, bool* isSimd) { if (IsNumericNonFloatLiteral(pn) || IsFloatLiteral(m, pn)) return true; if (IsSimdLiteral(m, pn)) { if (isSimd) *isSimd = true; return true; } return false; } // 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(PNK_NEG)) { pn = UnaryKid(pn); if (out) *out = pn; return -NumberNodeValue(pn); } return NumberNodeValue(pn); } static NumLit ExtractSimdValue(ModuleValidator& m, ParseNode* pn) { MOZ_ASSERT(IsSimdLiteral(m, pn)); SimdType type = SimdType::Count; JS_ALWAYS_TRUE(IsSimdTuple(m, pn, &type)); MOZ_ASSERT(CallArgListLength(pn) == GetSimdLanes(type)); ParseNode* arg = CallArgList(pn); switch (type) { case SimdType::Int8x16: case SimdType::Uint8x16: { MOZ_ASSERT(GetSimdLanes(type) == 16); int8_t val[16]; for (size_t i = 0; i < 16; i++, arg = NextNode(arg)) { uint32_t u32; JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); val[i] = int8_t(u32); } MOZ_ASSERT(arg == nullptr); NumLit::Which w = type == SimdType::Uint8x16 ? NumLit::Uint8x16 : NumLit::Int8x16; return NumLit(w, SimdConstant::CreateX16(val)); } case SimdType::Int16x8: case SimdType::Uint16x8: { MOZ_ASSERT(GetSimdLanes(type) == 8); int16_t val[8]; for (size_t i = 0; i < 8; i++, arg = NextNode(arg)) { uint32_t u32; JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); val[i] = int16_t(u32); } MOZ_ASSERT(arg == nullptr); NumLit::Which w = type == SimdType::Uint16x8 ? NumLit::Uint16x8 : NumLit::Int16x8; return NumLit(w, SimdConstant::CreateX8(val)); } case SimdType::Int32x4: case SimdType::Uint32x4: { MOZ_ASSERT(GetSimdLanes(type) == 4); int32_t val[4]; for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) { uint32_t u32; JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); val[i] = int32_t(u32); } MOZ_ASSERT(arg == nullptr); NumLit::Which w = type == SimdType::Uint32x4 ? NumLit::Uint32x4 : NumLit::Int32x4; return NumLit(w, SimdConstant::CreateX4(val)); } case SimdType::Float32x4: { MOZ_ASSERT(GetSimdLanes(type) == 4); float val[4]; for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) val[i] = float(ExtractNumericNonFloatValue(arg)); MOZ_ASSERT(arg == nullptr); return NumLit(NumLit::Float32x4, SimdConstant::CreateX4(val)); } case SimdType::Bool8x16: { MOZ_ASSERT(GetSimdLanes(type) == 16); int8_t val[16]; for (size_t i = 0; i < 16; i++, arg = NextNode(arg)) { uint32_t u32; JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); val[i] = u32 ? -1 : 0; } MOZ_ASSERT(arg == nullptr); return NumLit(NumLit::Bool8x16, SimdConstant::CreateX16(val)); } case SimdType::Bool16x8: { MOZ_ASSERT(GetSimdLanes(type) == 8); int16_t val[8]; for (size_t i = 0; i < 8; i++, arg = NextNode(arg)) { uint32_t u32; JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); val[i] = u32 ? -1 : 0; } MOZ_ASSERT(arg == nullptr); return NumLit(NumLit::Bool16x8, SimdConstant::CreateX8(val)); } case SimdType::Bool32x4: { MOZ_ASSERT(GetSimdLanes(type) == 4); int32_t val[4]; for (size_t i = 0; i < 4; i++, arg = NextNode(arg)) { uint32_t u32; JS_ALWAYS_TRUE(IsLiteralInt(m, arg, &u32)); val[i] = u32 ? -1 : 0; } MOZ_ASSERT(arg == nullptr); return NumLit(NumLit::Bool32x4, SimdConstant::CreateX4(val)); } default: break; } MOZ_CRASH("Unexpected SIMD type."); } static NumLit ExtractNumericLiteral(ModuleValidator& m, ParseNode* pn) { MOZ_ASSERT(IsNumericLiteral(m, pn)); if (pn->isKind(PNK_CALL)) { // Float literals are explicitly coerced and thus the coerced literal may be // any valid (non-float) numeric literal. if (CallArgListLength(pn) == 1) { pn = CallArgList(pn); double d = ExtractNumericNonFloatValue(pn); return NumLit(NumLit::Float, DoubleValue(d)); } return ExtractSimdValue(m, pn); } 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(!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: case NumLit::Int8x16: case NumLit::Uint8x16: case NumLit::Int16x8: case NumLit::Uint16x8: case NumLit::Int32x4: case NumLit::Uint32x4: case NumLit::Float32x4: case NumLit::Bool8x16: case NumLit::Bool16x8: case NumLit::Bool32x4: return false; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal type"); } static inline bool IsLiteralInt(ModuleValidator& m, ParseNode* pn, uint32_t* u32) { return IsNumericLiteral(m, pn) && IsLiteralInt(ExtractNumericLiteral(m, pn), u32); } /*****************************************************************************/ namespace { #define CASE(TYPE, OP) case SimdOperation::Fn_##OP: return Op::TYPE##OP; #define I8x16CASE(OP) CASE(I8x16, OP) #define I16x8CASE(OP) CASE(I16x8, OP) #define I32x4CASE(OP) CASE(I32x4, OP) #define F32x4CASE(OP) CASE(F32x4, OP) #define B8x16CASE(OP) CASE(B8x16, OP) #define B16x8CASE(OP) CASE(B16x8, OP) #define B32x4CASE(OP) CASE(B32x4, OP) #define ENUMERATE(TYPE, FOR_ALL, DO) \ switch(op) { \ case SimdOperation::Constructor: return Op::TYPE##Constructor; \ FOR_ALL(DO) \ default: break; \ } static inline Op SimdToOp(SimdType type, SimdOperation op) { switch (type) { case SimdType::Uint8x16: // Handle the special unsigned opcodes, then fall through to Int8x16. switch (op) { case SimdOperation::Fn_addSaturate: return Op::I8x16addSaturateU; case SimdOperation::Fn_subSaturate: return Op::I8x16subSaturateU; case SimdOperation::Fn_extractLane: return Op::I8x16extractLaneU; case SimdOperation::Fn_shiftRightByScalar: return Op::I8x16shiftRightByScalarU; case SimdOperation::Fn_lessThan: return Op::I8x16lessThanU; case SimdOperation::Fn_lessThanOrEqual: return Op::I8x16lessThanOrEqualU; case SimdOperation::Fn_greaterThan: return Op::I8x16greaterThanU; case SimdOperation::Fn_greaterThanOrEqual: return Op::I8x16greaterThanOrEqualU; case SimdOperation::Fn_fromInt8x16Bits: return Op::Limit; default: break; } MOZ_FALLTHROUGH; case SimdType::Int8x16: // Bitcasts Uint8x16 <--> Int8x16 become noops. switch (op) { case SimdOperation::Fn_fromUint8x16Bits: return Op::Limit; case SimdOperation::Fn_fromUint16x8Bits: return Op::I8x16fromInt16x8Bits; case SimdOperation::Fn_fromUint32x4Bits: return Op::I8x16fromInt32x4Bits; default: break; } ENUMERATE(I8x16, FORALL_INT8X16_ASMJS_OP, I8x16CASE) break; case SimdType::Uint16x8: // Handle the special unsigned opcodes, then fall through to Int16x8. switch(op) { case SimdOperation::Fn_addSaturate: return Op::I16x8addSaturateU; case SimdOperation::Fn_subSaturate: return Op::I16x8subSaturateU; case SimdOperation::Fn_extractLane: return Op::I16x8extractLaneU; case SimdOperation::Fn_shiftRightByScalar: return Op::I16x8shiftRightByScalarU; case SimdOperation::Fn_lessThan: return Op::I16x8lessThanU; case SimdOperation::Fn_lessThanOrEqual: return Op::I16x8lessThanOrEqualU; case SimdOperation::Fn_greaterThan: return Op::I16x8greaterThanU; case SimdOperation::Fn_greaterThanOrEqual: return Op::I16x8greaterThanOrEqualU; case SimdOperation::Fn_fromInt16x8Bits: return Op::Limit; default: break; } MOZ_FALLTHROUGH; case SimdType::Int16x8: // Bitcasts Uint16x8 <--> Int16x8 become noops. switch (op) { case SimdOperation::Fn_fromUint8x16Bits: return Op::I16x8fromInt8x16Bits; case SimdOperation::Fn_fromUint16x8Bits: return Op::Limit; case SimdOperation::Fn_fromUint32x4Bits: return Op::I16x8fromInt32x4Bits; default: break; } ENUMERATE(I16x8, FORALL_INT16X8_ASMJS_OP, I16x8CASE) break; case SimdType::Uint32x4: // Handle the special unsigned opcodes, then fall through to Int32x4. switch(op) { case SimdOperation::Fn_shiftRightByScalar: return Op::I32x4shiftRightByScalarU; case SimdOperation::Fn_lessThan: return Op::I32x4lessThanU; case SimdOperation::Fn_lessThanOrEqual: return Op::I32x4lessThanOrEqualU; case SimdOperation::Fn_greaterThan: return Op::I32x4greaterThanU; case SimdOperation::Fn_greaterThanOrEqual: return Op::I32x4greaterThanOrEqualU; case SimdOperation::Fn_fromFloat32x4: return Op::I32x4fromFloat32x4U; case SimdOperation::Fn_fromInt32x4Bits: return Op::Limit; default: break; } MOZ_FALLTHROUGH; case SimdType::Int32x4: // Bitcasts Uint32x4 <--> Int32x4 become noops. switch (op) { case SimdOperation::Fn_fromUint8x16Bits: return Op::I32x4fromInt8x16Bits; case SimdOperation::Fn_fromUint16x8Bits: return Op::I32x4fromInt16x8Bits; case SimdOperation::Fn_fromUint32x4Bits: return Op::Limit; default: break; } ENUMERATE(I32x4, FORALL_INT32X4_ASMJS_OP, I32x4CASE) break; case SimdType::Float32x4: switch (op) { case SimdOperation::Fn_fromUint8x16Bits: return Op::F32x4fromInt8x16Bits; case SimdOperation::Fn_fromUint16x8Bits: return Op::F32x4fromInt16x8Bits; case SimdOperation::Fn_fromUint32x4Bits: return Op::F32x4fromInt32x4Bits; default: break; } ENUMERATE(F32x4, FORALL_FLOAT32X4_ASMJS_OP, F32x4CASE) break; case SimdType::Bool8x16: ENUMERATE(B8x16, FORALL_BOOL_SIMD_OP, B8x16CASE) break; case SimdType::Bool16x8: ENUMERATE(B16x8, FORALL_BOOL_SIMD_OP, B16x8CASE) break; case SimdType::Bool32x4: ENUMERATE(B32x4, FORALL_BOOL_SIMD_OP, B32x4CASE) break; default: break; } MOZ_CRASH("unexpected SIMD (type, operator) combination"); } #undef CASE #undef I8x16CASE #undef I16x8CASE #undef I32x4CASE #undef F32x4CASE #undef B8x16CASE #undef B16x8CASE #undef B32x4CASE #undef ENUMERATE typedef Vector NameVector; // 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. class MOZ_STACK_CLASS FunctionValidator { public: struct Local { Type type; unsigned slot; Local(Type t, unsigned slot) : type(t), slot(slot) { MOZ_ASSERT(type.isCanonicalValType()); } }; private: typedef HashMap LocalMap; typedef HashMap LabelMap; ModuleValidator& m_; ParseNode* fn_; FunctionGenerator fg_; Maybe encoder_; LocalMap locals_; // Labels LabelMap breakLabels_; LabelMap continueLabels_; Uint32Vector breakableStack_; Uint32Vector continuableStack_; uint32_t blockDepth_; bool hasAlreadyReturned_; ExprType ret_; public: FunctionValidator(ModuleValidator& m, ParseNode* fn) : m_(m), fn_(fn), locals_(m.cx()), breakLabels_(m.cx()), continueLabels_(m.cx()), blockDepth_(0), hasAlreadyReturned_(false), ret_(ExprType::Limit) {} ModuleValidator& m() const { return m_; } ExclusiveContext* cx() const { return m_.cx(); } ParseNode* fn() const { return fn_; } bool init(PropertyName* name, unsigned line) { if (!locals_.init() || !breakLabels_.init() || !continueLabels_.init()) return false; if (!m_.mg().startFuncDef(line, &fg_)) return false; encoder_.emplace(fg_.bytes()); return true; } bool finish(uint32_t funcIndex) { MOZ_ASSERT(!blockDepth_); MOZ_ASSERT(breakableStack_.empty()); MOZ_ASSERT(continuableStack_.empty()); MOZ_ASSERT(breakLabels_.empty()); MOZ_ASSERT(continueLabels_.empty()); for (auto iter = locals_.all(); !iter.empty(); iter.popFront()) { if (iter.front().value().type.isSimd()) { setUsesSimd(); break; } } return m_.mg().finishFuncDef(funcIndex, &fg_); } bool fail(ParseNode* pn, const char* str) { return m_.fail(pn, str); } bool failf(ParseNode* pn, const char* fmt, ...) MOZ_FORMAT_PRINTF(3, 4) { va_list ap; va_start(ap, fmt); m_.failfVAOffset(pn->pn_pos.begin, fmt, ap); va_end(ap); return false; } bool failName(ParseNode* pn, const char* fmt, PropertyName* name) { return m_.failName(pn, fmt, name); } /***************************************************** Attributes */ void setUsesSimd() { fg_.setUsesSimd(); } void setUsesAtomics() { fg_.setUsesAtomics(); } /***************************************************** Local scope setup */ bool addLocal(ParseNode* pn, PropertyName* name, Type type) { LocalMap::AddPtr p = locals_.lookupForAdd(name); if (p) return failName(pn, "duplicate local name '%s' not allowed", name); return locals_.add(p, name, Local(type, locals_.count())); } /****************************** For consistency of returns in a function */ bool hasAlreadyReturned() const { return hasAlreadyReturned_; } ExprType returnedType() const { return ret_; } void setReturnedType(ExprType ret) { 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(PropertyName* label, LabelMap* map) { LabelMap::Ptr p = map->lookup(label); MOZ_ASSERT(p); map->remove(p); } public: bool pushBreakableBlock() { return encoder().writeOp(Op::Block) && encoder().writeFixedU8(uint8_t(ExprType::Void)) && breakableStack_.append(blockDepth_++); } bool popBreakableBlock() { JS_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_); return encoder().writeOp(Op::End); } bool pushUnbreakableBlock(const NameVector* labels = nullptr) { if (labels) { for (PropertyName* label : *labels) { if (!breakLabels_.putNew(label, blockDepth_)) return false; } } blockDepth_++; return encoder().writeOp(Op::Block) && encoder().writeFixedU8(uint8_t(ExprType::Void)); } bool popUnbreakableBlock(const NameVector* labels = nullptr) { if (labels) { for (PropertyName* label : *labels) removeLabel(label, &breakLabels_); } --blockDepth_; return encoder().writeOp(Op::End); } bool pushContinuableBlock() { return encoder().writeOp(Op::Block) && encoder().writeFixedU8(uint8_t(ExprType::Void)) && continuableStack_.append(blockDepth_++); } bool popContinuableBlock() { JS_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_); return encoder().writeOp(Op::End); } bool pushLoop() { return encoder().writeOp(Op::Block) && encoder().writeFixedU8(uint8_t(ExprType::Void)) && encoder().writeOp(Op::Loop) && encoder().writeFixedU8(uint8_t(ExprType::Void)) && breakableStack_.append(blockDepth_++) && continuableStack_.append(blockDepth_++); } bool popLoop() { JS_ALWAYS_TRUE(continuableStack_.popCopy() == --blockDepth_); JS_ALWAYS_TRUE(breakableStack_.popCopy() == --blockDepth_); return encoder().writeOp(Op::End) && encoder().writeOp(Op::End); } bool pushIf(size_t* typeAt) { ++blockDepth_; return encoder().writeOp(Op::If) && encoder().writePatchableFixedU7(typeAt); } bool switchToElse() { MOZ_ASSERT(blockDepth_ > 0); return encoder().writeOp(Op::Else); } void setIfType(size_t typeAt, ExprType type) { encoder().patchFixedU7(typeAt, uint8_t(type)); } bool popIf() { MOZ_ASSERT(blockDepth_ > 0); --blockDepth_; return encoder().writeOp(Op::End); } bool popIf(size_t typeAt, ExprType type) { MOZ_ASSERT(blockDepth_ > 0); --blockDepth_; if (!encoder().writeOp(Op::End)) return false; setIfType(typeAt, type); return true; } bool writeBreakIf() { return writeBr(breakableStack_.back(), Op::BrIf); } bool writeContinueIf() { return writeBr(continuableStack_.back(), Op::BrIf); } bool writeUnlabeledBreakOrContinue(bool isBreak) { return writeBr(isBreak? breakableStack_.back() : continuableStack_.back()); } bool writeContinue() { return writeBr(continuableStack_.back()); } bool addLabels(const NameVector& labels, uint32_t relativeBreakDepth, uint32_t relativeContinueDepth) { for (PropertyName* label : labels) { if (!breakLabels_.putNew(label, blockDepth_ + relativeBreakDepth)) return false; if (!continueLabels_.putNew(label, blockDepth_ + relativeContinueDepth)) return false; } return true; } void removeLabels(const NameVector& labels) { for (PropertyName* label : labels) { removeLabel(label, &breakLabels_); removeLabel(label, &continueLabels_); } } bool writeLabeledBreakOrContinue(PropertyName* label, bool isBreak) { LabelMap& map = isBreak ? breakLabels_ : continueLabels_; if (LabelMap::Ptr p = map.lookup(label)) return writeBr(p->value()); MOZ_CRASH("nonexistent label"); } /*************************************************** Read-only interface */ const Local* lookupLocal(PropertyName* name) const { if (auto p = locals_.lookup(name)) return &p->value(); return nullptr; } const ModuleValidator::Global* lookupGlobal(PropertyName* 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_; } MOZ_MUST_USE bool writeInt32Lit(int32_t i32) { return encoder().writeOp(Op::I32Const) && encoder().writeVarS32(i32); } MOZ_MUST_USE bool writeConstExpr(const NumLit& lit) { switch (lit.which()) { case NumLit::Fixnum: case NumLit::NegativeInt: case NumLit::BigUnsigned: return writeInt32Lit(lit.toInt32()); case NumLit::Float: return encoder().writeOp(Op::F32Const) && encoder().writeFixedF32(lit.toFloat()); case NumLit::Double: return encoder().writeOp(Op::F64Const) && encoder().writeFixedF64(lit.toDouble()); case NumLit::Int8x16: case NumLit::Uint8x16: return encoder().writeOp(Op::I8x16Const) && encoder().writeFixedI8x16(lit.simdValue().asInt8x16()); case NumLit::Int16x8: case NumLit::Uint16x8: return encoder().writeOp(Op::I16x8Const) && encoder().writeFixedI16x8(lit.simdValue().asInt16x8()); case NumLit::Int32x4: case NumLit::Uint32x4: return encoder().writeOp(Op::I32x4Const) && encoder().writeFixedI32x4(lit.simdValue().asInt32x4()); case NumLit::Float32x4: return encoder().writeOp(Op::F32x4Const) && encoder().writeFixedF32x4(lit.simdValue().asFloat32x4()); case NumLit::Bool8x16: // Boolean vectors use the Int8x16 memory representation. return encoder().writeOp(Op::B8x16Const) && encoder().writeFixedI8x16(lit.simdValue().asInt8x16()); case NumLit::Bool16x8: // Boolean vectors use the Int16x8 memory representation. return encoder().writeOp(Op::B16x8Const) && encoder().writeFixedI16x8(lit.simdValue().asInt16x8()); case NumLit::Bool32x4: // Boolean vectors use the Int32x4 memory representation. return encoder().writeOp(Op::B32x4Const) && encoder().writeFixedI32x4(lit.simdValue().asInt32x4()); case NumLit::OutOfRangeInt: break; } MOZ_CRASH("unexpected literal type"); } MOZ_MUST_USE bool writeCall(ParseNode* pn, Op op) { return encoder().writeOp(op) && fg_.addCallSiteLineNum(m().tokenStream().srcCoords.lineNum(pn->pn_pos.begin)); } MOZ_MUST_USE bool prepareCall(ParseNode* pn) { return fg_.addCallSiteLineNum(m().tokenStream().srcCoords.lineNum(pn->pn_pos.begin)); } MOZ_MUST_USE bool writeSimdOp(SimdType simdType, SimdOperation simdOp) { Op op = SimdToOp(simdType, simdOp); if (op == Op::Limit) return true; return encoder().writeOp(op); } }; } /* anonymous namespace */ /*****************************************************************************/ // asm.js type-checking and code-generation algorithm static bool CheckIdentifier(ModuleValidator& m, ParseNode* usepn, PropertyName* name) { if (name == m.cx()->names().arguments || name == m.cx()->names().eval) return m.failName(usepn, "'%s' is not an allowed identifier", name); return true; } static bool CheckModuleLevelName(ModuleValidator& m, ParseNode* usepn, PropertyName* name) { if (!CheckIdentifier(m, usepn, name)) return false; if (name == m.moduleFunctionName() || name == m.globalArgumentName() || name == m.importArgumentName() || name == m.bufferArgumentName() || m.lookupGlobal(name)) { return m.failName(usepn, "duplicate name '%s' not allowed", name); } return true; } static bool CheckFunctionHead(ModuleValidator& m, ParseNode* fn) { if (fn->pn_funbox->hasRest()) return m.fail(fn, "rest args not allowed"); if (fn->pn_funbox->isExprBody()) return m.fail(fn, "expression closures not allowed"); if (fn->pn_funbox->hasDestructuringArgs) return m.fail(fn, "destructuring args not allowed"); return true; } static bool CheckArgument(ModuleValidator& m, ParseNode* arg, PropertyName** name) { *name = nullptr; if (!arg->isKind(PNK_NAME)) return m.fail(arg, "argument is not a plain name"); if (!CheckIdentifier(m, arg, arg->name())) return false; *name = arg->name(); return true; } static bool CheckModuleArgument(ModuleValidator& m, ParseNode* arg, PropertyName** name) { if (!CheckArgument(m, arg, name)) return false; if (!CheckModuleLevelName(m, arg, *name)) return false; return true; } static bool CheckModuleArguments(ModuleValidator& m, ParseNode* fn) { unsigned numFormals; ParseNode* arg1 = FunctionFormalParametersList(fn, &numFormals); ParseNode* arg2 = arg1 ? NextNode(arg1) : nullptr; ParseNode* arg3 = arg2 ? NextNode(arg2) : nullptr; if (numFormals > 3) return m.fail(fn, "asm.js modules takes at most 3 argument"); PropertyName* arg1Name = nullptr; if (arg1 && !CheckModuleArgument(m, arg1, &arg1Name)) return false; if (!m.initGlobalArgumentName(arg1Name)) return false; PropertyName* arg2Name = nullptr; if (arg2 && !CheckModuleArgument(m, arg2, &arg2Name)) return false; if (!m.initImportArgumentName(arg2Name)) return false; PropertyName* arg3Name = nullptr; if (arg3 && !CheckModuleArgument(m, arg3, &arg3Name)) return false; if (!m.initBufferArgumentName(arg3Name)) return false; return true; } static bool CheckPrecedingStatements(ModuleValidator& m, ParseNode* stmtList) { MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST)); ParseNode* stmt = ListHead(stmtList); for (unsigned i = 0, n = ListLength(stmtList); i < n; i++) { if (!IsIgnoredDirective(m.cx(), stmt)) return m.fail(stmt, "invalid asm.js statement"); } return true; } static bool CheckGlobalVariableInitConstant(ModuleValidator& m, PropertyName* varName, ParseNode* initNode, bool isConst) { NumLit lit = ExtractNumericLiteral(m, initNode); if (!lit.valid()) return m.fail(initNode, "global initializer is out of representable integer range"); Type canonicalType = Type::canonicalize(Type::lit(lit)); if (!canonicalType.isGlobalVarType()) return m.fail(initNode, "global variable type not allowed"); return m.addGlobalVarInit(varName, lit, canonicalType, isConst); } static bool CheckTypeAnnotation(ModuleValidator& m, ParseNode* coercionNode, Type* coerceTo, ParseNode** coercedExpr = nullptr) { switch (coercionNode->getKind()) { case PNK_BITOR: { 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 PNK_POS: { *coerceTo = Type::Double; if (coercedExpr) *coercedExpr = UnaryKid(coercionNode); return true; } case PNK_CALL: { if (IsCoercionCall(m, coercionNode, coerceTo, coercedExpr)) return true; break; } default:; } return m.fail(coercionNode, "must be of the form +x, x|0, fround(x), or a SIMD check(x)"); } static bool CheckGlobalVariableInitImport(ModuleValidator& m, PropertyName* varName, ParseNode* initNode, bool isConst) { Type coerceTo; ParseNode* coercedExpr; if (!CheckTypeAnnotation(m, initNode, &coerceTo, &coercedExpr)) return false; if (!coercedExpr->isKind(PNK_DOT)) return m.failName(coercedExpr, "invalid import expression for global '%s'", varName); if (!coerceTo.isGlobalVarType()) return m.fail(initNode, "global variable type not allowed"); ParseNode* base = DotBase(coercedExpr); PropertyName* field = DotMember(coercedExpr); PropertyName* importName = m.importArgumentName(); if (!importName) return m.fail(coercedExpr, "cannot import without an asm.js foreign parameter"); if (!IsUseOfName(base, importName)) return m.failName(coercedExpr, "base of import expression must be '%s'", importName); return m.addGlobalVarImport(varName, field, coerceTo, isConst); } static bool IsArrayViewCtorName(ModuleValidator& m, PropertyName* name, Scalar::Type* type) { JSAtomState& names = m.cx()->names(); if (name == names.Int8Array) { *type = Scalar::Int8; } else if (name == names.Uint8Array) { *type = Scalar::Uint8; } else if (name == names.Int16Array) { *type = Scalar::Int16; } else if (name == names.Uint16Array) { *type = Scalar::Uint16; } else if (name == names.Int32Array) { *type = Scalar::Int32; } else if (name == names.Uint32Array) { *type = Scalar::Uint32; } else if (name == names.Float32Array) { *type = Scalar::Float32; } else if (name == names.Float64Array) { *type = Scalar::Float64; } else { return false; } return true; } static bool CheckNewArrayViewArgs(ModuleValidator& m, ParseNode* ctorExpr, PropertyName* bufferName) { ParseNode* bufArg = NextNode(ctorExpr); 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(ModuleValidator& m, PropertyName* varName, ParseNode* newExpr) { PropertyName* globalName = m.globalArgumentName(); if (!globalName) return m.fail(newExpr, "cannot create array view without an asm.js global parameter"); PropertyName* bufferName = m.bufferArgumentName(); if (!bufferName) return m.fail(newExpr, "cannot create array view without an asm.js heap parameter"); ParseNode* ctorExpr = ListHead(newExpr); PropertyName* field; Scalar::Type type; if (ctorExpr->isKind(PNK_DOT)) { 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(PNK_NAME)) return m.fail(ctorExpr, "expecting name of imported array view constructor"); PropertyName* globalName = ctorExpr->name(); const ModuleValidator::Global* global = m.lookupGlobal(globalName); if (!global) return m.failName(ctorExpr, "%s not found in module global scope", globalName); if (global->which() != ModuleValidator::Global::ArrayViewCtor) return m.failName(ctorExpr, "%s must be an imported array view constructor", globalName); field = nullptr; type = global->viewType(); } if (!CheckNewArrayViewArgs(m, ctorExpr, bufferName)) return false; return m.addArrayView(varName, type, field); } static bool IsSimdValidOperationType(SimdType type, SimdOperation op) { #define CASE(op) case SimdOperation::Fn_##op: switch(type) { case SimdType::Int8x16: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromUint8x16Bits: case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromUint32x4Bits: FORALL_INT8X16_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Int16x8: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromUint8x16Bits: case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromUint32x4Bits: FORALL_INT16X8_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Int32x4: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromUint8x16Bits: case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromUint32x4Bits: FORALL_INT32X4_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Uint8x16: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromInt8x16Bits: case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromUint32x4Bits: FORALL_INT8X16_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Uint16x8: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromUint8x16Bits: case SimdOperation::Fn_fromInt16x8Bits: case SimdOperation::Fn_fromUint32x4Bits: FORALL_INT16X8_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Uint32x4: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromUint8x16Bits: case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromInt32x4Bits: FORALL_INT32X4_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Float32x4: switch (op) { case SimdOperation::Constructor: case SimdOperation::Fn_fromUint8x16Bits: case SimdOperation::Fn_fromUint16x8Bits: case SimdOperation::Fn_fromUint32x4Bits: FORALL_FLOAT32X4_ASMJS_OP(CASE) return true; default: return false; } break; case SimdType::Bool8x16: case SimdType::Bool16x8: case SimdType::Bool32x4: switch (op) { case SimdOperation::Constructor: FORALL_BOOL_SIMD_OP(CASE) return true; default: return false; } break; default: // Unimplemented SIMD type. return false; } #undef CASE } static bool CheckGlobalMathImport(ModuleValidator& m, ParseNode* initNode, PropertyName* varName, PropertyName* field) { // Math builtin, with the form glob.Math.[[builtin]] ModuleValidator::MathBuiltin mathBuiltin; if (!m.lookupStandardLibraryMathName(field, &mathBuiltin)) return m.failName(initNode, "'%s' is not a standard Math builtin", field); switch (mathBuiltin.kind) { case ModuleValidator::MathBuiltin::Function: return m.addMathBuiltinFunction(varName, mathBuiltin.u.func, field); case ModuleValidator::MathBuiltin::Constant: return m.addMathBuiltinConstant(varName, mathBuiltin.u.cst, field); default: break; } MOZ_CRASH("unexpected or uninitialized math builtin type"); } static bool CheckGlobalAtomicsImport(ModuleValidator& m, ParseNode* initNode, PropertyName* varName, PropertyName* field) { // Atomics builtin, with the form glob.Atomics.[[builtin]] AsmJSAtomicsBuiltinFunction func; if (!m.lookupStandardLibraryAtomicsName(field, &func)) return m.failName(initNode, "'%s' is not a standard Atomics builtin", field); return m.addAtomicsBuiltinFunction(varName, func, field); } static bool CheckGlobalSimdImport(ModuleValidator& m, ParseNode* initNode, PropertyName* varName, PropertyName* field) { if (!m.supportsSimd()) return m.fail(initNode, "SIMD is not supported on this platform"); // SIMD constructor, with the form glob.SIMD.[[type]] SimdType simdType; if (!IsSimdTypeName(m.cx()->names(), field, &simdType)) return m.failName(initNode, "'%s' is not a standard SIMD type", field); // IsSimdTypeName will return true for any SIMD type supported by the VM. // // Since we may not support all of those SIMD types in asm.js, use the // asm.js-specific IsSimdValidOperationType() to check if this specific // constructor is supported in asm.js. if (!IsSimdValidOperationType(simdType, SimdOperation::Constructor)) return m.failName(initNode, "'%s' is not a supported SIMD type", field); return m.addSimdCtor(varName, simdType, field); } static bool CheckGlobalSimdOperationImport(ModuleValidator& m, const ModuleValidator::Global* global, ParseNode* initNode, PropertyName* varName, PropertyName* opName) { SimdType simdType = global->simdCtorType(); SimdOperation simdOp; if (!m.lookupStandardSimdOpName(opName, &simdOp)) return m.failName(initNode, "'%s' is not a standard SIMD operation", opName); if (!IsSimdValidOperationType(simdType, simdOp)) return m.failName(initNode, "'%s' is not an operation supported by the SIMD type", opName); return m.addSimdOperation(varName, simdType, simdOp, opName); } static bool CheckGlobalDotImport(ModuleValidator& m, PropertyName* varName, ParseNode* initNode) { ParseNode* base = DotBase(initNode); PropertyName* field = DotMember(initNode); if (base->isKind(PNK_DOT)) { ParseNode* global = DotBase(base); PropertyName* mathOrAtomicsOrSimd = DotMember(base); PropertyName* globalName = m.globalArgumentName(); if (!globalName) return m.fail(base, "import statement requires the module have a stdlib parameter"); if (!IsUseOfName(global, globalName)) { if (global->isKind(PNK_DOT)) { 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 (mathOrAtomicsOrSimd == m.cx()->names().Math) return CheckGlobalMathImport(m, initNode, varName, field); if (mathOrAtomicsOrSimd == m.cx()->names().Atomics) return CheckGlobalAtomicsImport(m, initNode, varName, field); if (mathOrAtomicsOrSimd == m.cx()->names().SIMD) return CheckGlobalSimdImport(m, initNode, varName, field); return m.failName(base, "expecting %s.{Math|SIMD}", globalName); } if (!base->isKind(PNK_NAME)) return m.fail(base, "expected name of variable or parameter"); if (base->name() == m.globalArgumentName()) { if (field == m.cx()->names().NaN) return m.addGlobalConstant(varName, GenericNaN(), field); if (field == m.cx()->names().Infinity) return m.addGlobalConstant(varName, PositiveInfinity(), 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 (base->name() == m.importArgumentName()) return m.addFFI(varName, field); const ModuleValidator::Global* global = m.lookupGlobal(base->name()); if (!global) return m.failName(initNode, "%s not found in module global scope", base->name()); if (!global->isSimdCtor()) return m.failName(base, "expecting SIMD constructor name, got %s", field); return CheckGlobalSimdOperationImport(m, global, initNode, varName, field); } static bool CheckModuleGlobal(ModuleValidator& m, ParseNode* var, bool isConst) { if (!var->isKind(PNK_NAME)) return m.fail(var, "import variable is not a plain name"); if (!CheckModuleLevelName(m, var, var->name())) return false; ParseNode* initNode = MaybeInitializer(var); if (!initNode) return m.fail(var, "module import needs initializer"); if (IsNumericLiteral(m, initNode)) return CheckGlobalVariableInitConstant(m, var->name(), initNode, isConst); if (initNode->isKind(PNK_BITOR) || initNode->isKind(PNK_POS) || initNode->isKind(PNK_CALL)) return CheckGlobalVariableInitImport(m, var->name(), initNode, isConst); if (initNode->isKind(PNK_NEW)) return CheckNewArrayView(m, var->name(), initNode); if (initNode->isKind(PNK_DOT)) return CheckGlobalDotImport(m, var->name(), initNode); return m.fail(initNode, "unsupported import expression"); } static bool CheckModuleProcessingDirectives(ModuleValidator& m) { TokenStream& ts = m.parser().tokenStream; while (true) { bool matched; if (!ts.matchToken(&matched, TOK_STRING, TokenStream::Operand)) return false; if (!matched) return true; if (!IsIgnoredDirectiveName(m.cx(), ts.currentToken().atom())) return m.failCurrentOffset("unsupported processing directive"); TokenKind tt; if (!ts.getToken(&tt)) return false; if (tt != TOK_SEMI) return m.failCurrentOffset("expected semicolon after string literal"); } } 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(PNK_CONST))) return false; } } return true; } static bool ArgFail(FunctionValidator& f, PropertyName* argName, ParseNode* stmt) { return f.failName(stmt, "expecting argument type declaration for '%s' of the " "form 'arg = arg|0' or 'arg = +arg' or 'arg = fround(arg)'", argName); } static bool CheckArgumentType(FunctionValidator& f, ParseNode* stmt, PropertyName* name, Type* type) { if (!stmt || !IsExpressionStatement(stmt)) return ArgFail(f, name, stmt ? stmt : f.fn()); ParseNode* initNode = ExpressionStatementExpr(stmt); if (!initNode || !initNode->isKind(PNK_ASSIGN)) 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(ModuleValidator& m, ParseNode** stmtIter) { ParseNode* stmt = *stmtIter; while (stmt && IsIgnoredDirective(m.cx(), stmt)) stmt = NextNode(stmt); *stmtIter = stmt; return true; } static bool CheckArguments(FunctionValidator& f, ParseNode** stmtIter, ValTypeVector* argTypes) { ParseNode* stmt = *stmtIter; unsigned numFormals; ParseNode* argpn = FunctionFormalParametersList(f.fn(), &numFormals); for (unsigned i = 0; i < numFormals; i++, argpn = NextNode(argpn), stmt = NextNode(stmt)) { PropertyName* name; if (!CheckArgument(f.m(), argpn, &name)) return false; Type type; if (!CheckArgumentType(f, stmt, name, &type)) return false; if (!argTypes->append(type.canonicalToValType())) return false; if (!f.addLocal(argpn, name, type)) return false; } *stmtIter = stmt; return true; } static bool IsLiteralOrConst(FunctionValidator& f, ParseNode* pn, NumLit* lit) { if (pn->isKind(PNK_NAME)) { const ModuleValidator::Global* global = f.lookupGlobal(pn->name()); if (!global || global->which() != ModuleValidator::Global::ConstantLiteral) return false; *lit = global->constLiteralValue(); return true; } bool isSimd = false; if (!IsNumericLiteral(f.m(), pn, &isSimd)) return false; if (isSimd) f.setUsesSimd(); *lit = ExtractNumericLiteral(f.m(), pn); return true; } static bool CheckFinalReturn(FunctionValidator& f, ParseNode* lastNonEmptyStmt) { if (!f.encoder().writeOp(Op::End)) return false; if (!f.hasAlreadyReturned()) { f.setReturnedType(ExprType::Void); return true; } if (!lastNonEmptyStmt->isKind(PNK_RETURN) && !IsVoid(f.returnedType())) return f.fail(lastNonEmptyStmt, "void incompatible with previous return type"); return true; } static bool CheckVariable(FunctionValidator& f, ParseNode* var, ValTypeVector* types, Vector* inits) { if (!var->isKind(PNK_NAME)) return f.fail(var, "local variable is not a plain name"); PropertyName* name = var->name(); if (!CheckIdentifier(f.m(), var, name)) return false; ParseNode* initNode = MaybeInitializer(var); if (!initNode) return f.failName(var, "var '%s' needs explicit type declaration via an initial value", name); 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(FunctionValidator& f, ParseNode** stmtIter) { ParseNode* stmt = *stmtIter; uint32_t firstVar = f.numLocals(); ValTypeVector types; Vector inits(f.cx()); for (; stmt && stmt->isKind(PNK_VAR); stmt = NextNonEmptyStatement(stmt)) { for (ParseNode* var = VarListHead(stmt); var; var = NextNode(var)) { if (!CheckVariable(f, var, &types, &inits)) return false; } } MOZ_ASSERT(f.encoder().empty()); if (!EncodeLocalEntries(f.encoder(), types)) return false; for (uint32_t i = 0; i < inits.length(); i++) { NumLit lit = inits[i]; if (lit.isZeroBits()) continue; if (!f.writeConstExpr(lit)) return false; if (!f.encoder().writeOp(Op::SetLocal)) return false; if (!f.encoder().writeVarU32(firstVar + i)) return false; } *stmtIter = stmt; return true; } static bool CheckExpr(FunctionValidator& f, ParseNode* op, Type* type); 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(FunctionValidator& f, ParseNode* varRef, Type* type) { PropertyName* name = varRef->name(); if (const FunctionValidator::Local* local = f.lookupLocal(name)) { if (!f.encoder().writeOp(Op::GetLocal)) return false; if (!f.encoder().writeVarU32(local->slot)) return false; *type = local->type; return true; } if (const ModuleValidator::Global* global = f.lookupGlobal(name)) { switch (global->which()) { case ModuleValidator::Global::ConstantLiteral: *type = global->varOrConstType(); return f.writeConstExpr(global->constLiteralValue()); case ModuleValidator::Global::ConstantImport: case ModuleValidator::Global::Variable: { *type = global->varOrConstType(); return f.encoder().writeOp(Op::GetGlobal) && f.encoder().writeVarU32(global->varOrConstIndex()); } case ModuleValidator::Global::Function: case ModuleValidator::Global::FFI: case ModuleValidator::Global::MathBuiltinFunction: case ModuleValidator::Global::AtomicsBuiltinFunction: case ModuleValidator::Global::FuncPtrTable: case ModuleValidator::Global::ArrayView: case ModuleValidator::Global::ArrayViewCtor: case ModuleValidator::Global::SimdCtor: case ModuleValidator::Global::SimdOp: 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(FunctionValidator& 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; static const bool YesSimd = true; static const bool NoSimd = false; static bool CheckArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr, bool isSimd, Scalar::Type* viewType) { if (!viewName->isKind(PNK_NAME)) return f.fail(viewName, "base of array access must be a typed array view name"); const ModuleValidator::Global* global = f.lookupGlobal(viewName->name()); if (!global || !global->isAnyArrayView()) return f.fail(viewName, "base of array access must be a typed array view name"); *viewType = global->viewType(); uint32_t index; if (IsLiteralOrConstInt(f, indexExpr, &index)) { uint64_t byteOffset = uint64_t(index) << TypedArrayShift(*viewType); uint64_t width = isSimd ? Simd128DataSize : 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(PNK_RSH)) { 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 SIMD access, and 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 (isSimd) { if (!pointerType.isIntish()) return f.failf(pointerNode, "%s is not a subtype of intish", pointerType.toChars()); } else { 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 or a SIMD access. if (mask != NoMask) { return f.writeInt32Lit(mask) && f.encoder().writeOp(Op::I32And); } return true; } static bool CheckAndPrepareArrayAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr, bool isSimd, Scalar::Type* viewType) { return CheckArrayAccess(f, viewName, indexExpr, isSimd, viewType); } static bool WriteArrayAccessFlags(FunctionValidator& 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; } static bool CheckLoadArray(FunctionValidator& f, ParseNode* elem, Type* type) { Scalar::Type viewType; if (!CheckAndPrepareArrayAccess(f, ElemBase(elem), ElemIndex(elem), NoSimd, &viewType)) return false; switch (viewType) { case Scalar::Int8: if (!f.encoder().writeOp(Op::I32Load8S)) return false; break; case Scalar::Uint8: if (!f.encoder().writeOp(Op::I32Load8U)) return false; break; case Scalar::Int16: if (!f.encoder().writeOp(Op::I32Load16S)) return false; break; case Scalar::Uint16: if (!f.encoder().writeOp(Op::I32Load16U)) return false; break; case Scalar::Uint32: case Scalar::Int32: if (!f.encoder().writeOp(Op::I32Load)) return false; break; case Scalar::Float32: if (!f.encoder().writeOp(Op::F32Load)) return false; break; case Scalar::Float64: if (!f.encoder().writeOp(Op::F64Load)) return false; break; default: MOZ_CRASH("unexpected scalar type"); } switch (viewType) { case Scalar::Int8: case Scalar::Int16: case Scalar::Int32: case Scalar::Uint8: case Scalar::Uint16: case Scalar::Uint32: *type = Type::Intish; break; case Scalar::Float32: *type = Type::MaybeFloat; break; case Scalar::Float64: *type = Type::MaybeDouble; break; default: MOZ_CRASH("Unexpected array type"); } if (!WriteArrayAccessFlags(f, viewType)) return false; return true; } static bool CheckStoreArray(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type) { Scalar::Type viewType; if (!CheckAndPrepareArrayAccess(f, ElemBase(lhs), ElemIndex(lhs), NoSimd, &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(Op::I32TeeStore8)) return false; break; case Scalar::Int16: case Scalar::Uint16: if (!f.encoder().writeOp(Op::I32TeeStore16)) return false; break; case Scalar::Int32: case Scalar::Uint32: if (!f.encoder().writeOp(Op::I32TeeStore)) return false; break; case Scalar::Float32: if (rhsType.isFloatish()) { if (!f.encoder().writeOp(Op::F32TeeStore)) return false; } else { if (!f.encoder().writeOp(Op::F64TeeStoreF32)) return false; } break; case Scalar::Float64: if (rhsType.isFloatish()) { if (!f.encoder().writeOp(Op::F32TeeStoreF64)) return false; } else { if (!f.encoder().writeOp(Op::F64TeeStore)) return false; } break; default: MOZ_CRASH("unexpected scalar type"); } if (!WriteArrayAccessFlags(f, viewType)) return false; *type = rhsType; return true; } static bool CheckAssignName(FunctionValidator& f, ParseNode* lhs, ParseNode* rhs, Type* type) { RootedPropertyName name(f.cx(), lhs->name()); if (const FunctionValidator::Local* lhsVar = f.lookupLocal(name)) { Type rhsType; if (!CheckExpr(f, rhs, &rhsType)) return false; if (!f.encoder().writeOp(Op::TeeLocal)) return false; if (!f.encoder().writeVarU32(lhsVar->slot)) return false; if (!(rhsType <= lhsVar->type)) { return f.failf(lhs, "%s is not a subtype of %s", rhsType.toChars(), lhsVar->type.toChars()); } *type = rhsType; return true; } if (const ModuleValidator::Global* global = f.lookupGlobal(name)) { if (global->which() != ModuleValidator::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(Op::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); } static bool CheckAssign(FunctionValidator& f, ParseNode* assign, Type* type) { MOZ_ASSERT(assign->isKind(PNK_ASSIGN)); ParseNode* lhs = BinaryLeft(assign); ParseNode* rhs = BinaryRight(assign); if (lhs->getKind() == PNK_ELEM) return CheckStoreArray(f, lhs, rhs, type); if (lhs->getKind() == PNK_NAME) return CheckAssignName(f, lhs, rhs, type); return f.fail(assign, "left-hand side of assignment must be a variable or array access"); } 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); } 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); } 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(Op::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()); } 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()); } 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; 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; op = isMax ? Op::I32Max : Op::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 (!f.encoder().writeOp(op)) return false; } return true; } static bool CheckSharedArrayAtomicAccess(FunctionValidator& f, ParseNode* viewName, ParseNode* indexExpr, Scalar::Type* viewType) { if (!CheckAndPrepareArrayAccess(f, viewName, indexExpr, NoSimd, viewType)) return false; // The global will be sane, CheckArrayAccess checks it. const ModuleValidator::Global* global = f.lookupGlobal(viewName->name()); if (global->which() != ModuleValidator::Global::ArrayView) return f.fail(viewName, "base of array access must be a typed array view"); MOZ_ASSERT(f.m().atomicsPresent()); switch (*viewType) { case Scalar::Int8: case Scalar::Int16: case Scalar::Int32: case Scalar::Uint8: case Scalar::Uint16: case Scalar::Uint32: return true; default: return f.failf(viewName, "not an integer array"); } return true; } static bool WriteAtomicOperator(FunctionValidator& f, Op opcode, Scalar::Type viewType) { return f.encoder().writeOp(opcode) && f.encoder().writeFixedU8(viewType); } static bool CheckAtomicsLoad(FunctionValidator& f, ParseNode* call, Type* type) { if (CallArgListLength(call) != 2) return f.fail(call, "Atomics.load must be passed 2 arguments"); ParseNode* arrayArg = CallArgList(call); ParseNode* indexArg = NextNode(arrayArg); Scalar::Type viewType; if (!CheckSharedArrayAtomicAccess(f, arrayArg, indexArg, &viewType)) return false; if (!WriteAtomicOperator(f, Op::I32AtomicsLoad, viewType)) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; *type = Type::Int; return true; } static bool CheckAtomicsStore(FunctionValidator& f, ParseNode* call, Type* type) { if (CallArgListLength(call) != 3) return f.fail(call, "Atomics.store must be passed 3 arguments"); ParseNode* arrayArg = CallArgList(call); ParseNode* indexArg = NextNode(arrayArg); ParseNode* valueArg = NextNode(indexArg); Type rhsType; if (!CheckExpr(f, valueArg, &rhsType)) return false; if (!rhsType.isIntish()) return f.failf(arrayArg, "%s is not a subtype of intish", rhsType.toChars()); Scalar::Type viewType; if (!CheckSharedArrayAtomicAccess(f, arrayArg, indexArg, &viewType)) return false; if (!WriteAtomicOperator(f, Op::I32AtomicsStore, viewType)) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; *type = rhsType; return true; } static bool CheckAtomicsBinop(FunctionValidator& f, ParseNode* call, Type* type, AtomicOp op) { if (CallArgListLength(call) != 3) return f.fail(call, "Atomics binary operator must be passed 3 arguments"); ParseNode* arrayArg = CallArgList(call); ParseNode* indexArg = NextNode(arrayArg); ParseNode* valueArg = NextNode(indexArg); Type valueArgType; if (!CheckExpr(f, valueArg, &valueArgType)) return false; if (!valueArgType.isIntish()) return f.failf(valueArg, "%s is not a subtype of intish", valueArgType.toChars()); Scalar::Type viewType; if (!CheckSharedArrayAtomicAccess(f, arrayArg, indexArg, &viewType)) return false; if (!WriteAtomicOperator(f, Op::I32AtomicsBinOp, viewType)) return false; if (!f.encoder().writeFixedU8(uint8_t(op))) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; *type = Type::Int; return true; } static bool CheckAtomicsIsLockFree(FunctionValidator& f, ParseNode* call, Type* type) { if (CallArgListLength(call) != 1) return f.fail(call, "Atomics.isLockFree must be passed 1 argument"); ParseNode* sizeArg = CallArgList(call); uint32_t size; if (!IsLiteralInt(f.m(), sizeArg, &size)) return f.fail(sizeArg, "Atomics.isLockFree requires an integer literal argument"); *type = Type::Int; return f.writeInt32Lit(AtomicOperations::isLockfree(size)); } static bool CheckAtomicsCompareExchange(FunctionValidator& f, ParseNode* call, Type* type) { if (CallArgListLength(call) != 4) return f.fail(call, "Atomics.compareExchange must be passed 4 arguments"); ParseNode* arrayArg = CallArgList(call); ParseNode* indexArg = NextNode(arrayArg); ParseNode* oldValueArg = NextNode(indexArg); ParseNode* newValueArg = NextNode(oldValueArg); Type oldValueArgType; if (!CheckExpr(f, oldValueArg, &oldValueArgType)) return false; Type newValueArgType; if (!CheckExpr(f, newValueArg, &newValueArgType)) return false; if (!oldValueArgType.isIntish()) return f.failf(oldValueArg, "%s is not a subtype of intish", oldValueArgType.toChars()); if (!newValueArgType.isIntish()) return f.failf(newValueArg, "%s is not a subtype of intish", newValueArgType.toChars()); Scalar::Type viewType; if (!CheckSharedArrayAtomicAccess(f, arrayArg, indexArg, &viewType)) return false; if (!WriteAtomicOperator(f, Op::I32AtomicsCompareExchange, viewType)) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; *type = Type::Int; return true; } static bool CheckAtomicsExchange(FunctionValidator& f, ParseNode* call, Type* type) { if (CallArgListLength(call) != 3) return f.fail(call, "Atomics.exchange must be passed 3 arguments"); ParseNode* arrayArg = CallArgList(call); ParseNode* indexArg = NextNode(arrayArg); ParseNode* valueArg = NextNode(indexArg); Type valueArgType; if (!CheckExpr(f, valueArg, &valueArgType)) return false; if (!valueArgType.isIntish()) return f.failf(arrayArg, "%s is not a subtype of intish", valueArgType.toChars()); Scalar::Type viewType; if (!CheckSharedArrayAtomicAccess(f, arrayArg, indexArg, &viewType)) return false; if (!WriteAtomicOperator(f, Op::I32AtomicsExchange, viewType)) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; *type = Type::Int; return true; } static bool CheckAtomicsBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSAtomicsBuiltinFunction func, Type* type) { f.setUsesAtomics(); switch (func) { case AsmJSAtomicsBuiltin_compareExchange: return CheckAtomicsCompareExchange(f, callNode, type); case AsmJSAtomicsBuiltin_exchange: return CheckAtomicsExchange(f, callNode, type); case AsmJSAtomicsBuiltin_load: return CheckAtomicsLoad(f, callNode, type); case AsmJSAtomicsBuiltin_store: return CheckAtomicsStore(f, callNode, type); case AsmJSAtomicsBuiltin_add: return CheckAtomicsBinop(f, callNode, type, AtomicFetchAddOp); case AsmJSAtomicsBuiltin_sub: return CheckAtomicsBinop(f, callNode, type, AtomicFetchSubOp); case AsmJSAtomicsBuiltin_and: return CheckAtomicsBinop(f, callNode, type, AtomicFetchAndOp); case AsmJSAtomicsBuiltin_or: return CheckAtomicsBinop(f, callNode, type, AtomicFetchOrOp); case AsmJSAtomicsBuiltin_xor: return CheckAtomicsBinop(f, callNode, type, AtomicFetchXorOp); case AsmJSAtomicsBuiltin_isLockFree: return CheckAtomicsIsLockFree(f, callNode, type); default: MOZ_CRASH("unexpected atomicsBuiltin function"); } } typedef bool (*CheckArgType)(FunctionValidator& 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(ModuleValidator& m, ParseNode* usepn, const Sig& sig, const Sig& existing) { if (sig.args().length() != existing.args().length()) { return m.failf(usepn, "incompatible number of arguments (%" PRIuSIZE " here vs. %" PRIuSIZE " before)", sig.args().length(), existing.args().length()); } for (unsigned i = 0; i < sig.args().length(); i++) { if (sig.arg(i) != existing.arg(i)) { return m.failf(usepn, "incompatible type for argument %u: (%s here vs. %s before)", i, ToCString(sig.arg(i)), ToCString(existing.arg(i))); } } if (sig.ret() != existing.ret()) { return m.failf(usepn, "%s incompatible with previous return of type %s", ToCString(sig.ret()), ToCString(existing.ret())); } MOZ_ASSERT(sig == existing); return true; } static bool CheckFunctionSignature(ModuleValidator& m, ParseNode* usepn, Sig&& sig, PropertyName* name, ModuleValidator::Func** func) { ModuleValidator::Func* existing = m.lookupFunction(name); if (!existing) { if (!CheckModuleLevelName(m, usepn, name)) return false; return m.addFunction(name, usepn->pn_pos.begin, Move(sig), func); } if (!CheckSignatureAgainstExisting(m, usepn, sig, m.mg().funcSig(existing->index()))) return false; *func = existing; return true; } static bool CheckIsArgType(FunctionValidator& f, ParseNode* argNode, Type type) { if (!type.isArgType()) return f.failf(argNode, "%s is not a subtype of int, float, double, or an allowed SIMD type", type.toChars()); return true; } static bool CheckInternalCall(FunctionValidator& f, ParseNode* callNode, PropertyName* calleeName, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); ValTypeVector args; if (!CheckCallArgs(f, callNode, &args)) return false; Sig sig(Move(args), ret.canonicalToExprType()); ModuleValidator::Func* callee; if (!CheckFunctionSignature(f.m(), callNode, Move(sig), calleeName, &callee)) return false; if (!f.writeCall(callNode, Op::Call)) return false; if (!f.encoder().writeVarU32(callee->index())) return false; *type = Type::ret(ret); return true; } static bool CheckFuncPtrTableAgainstExisting(ModuleValidator& m, ParseNode* usepn, PropertyName* name, Sig&& sig, unsigned mask, uint32_t* funcPtrTableIndex) { if (const ModuleValidator::Global* existing = m.lookupGlobal(name)) { if (existing->which() != ModuleValidator::Global::FuncPtrTable) return m.failName(usepn, "'%s' is not a function-pointer table", name); ModuleValidator::FuncPtrTable& table = m.funcPtrTable(existing->funcPtrTableIndex()); if (mask != table.mask()) return m.failf(usepn, "mask does not match previous value (%u)", table.mask()); if (!CheckSignatureAgainstExisting(m, usepn, sig, m.mg().sig(table.sigIndex()))) return false; *funcPtrTableIndex = existing->funcPtrTableIndex(); return true; } if (!CheckModuleLevelName(m, usepn, name)) return false; if (!m.declareFuncPtrTable(Move(sig), name, usepn->pn_pos.begin, mask, funcPtrTableIndex)) return false; return true; } 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(PNK_NAME)) return f.fail(tableNode, "expecting name of function-pointer array"); PropertyName* name = tableNode->name(); if (const ModuleValidator::Global* existing = f.lookupGlobal(name)) { if (existing->which() != ModuleValidator::Global::FuncPtrTable) return f.failName(tableNode, "'%s' is not the name of a function-pointer array", name); } if (!indexExpr->isKind(PNK_BITAND)) 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; Sig sig(Move(args), ret.canonicalToExprType()); uint32_t tableIndex; if (!CheckFuncPtrTableAgainstExisting(f.m(), tableNode, name, Move(sig), mask, &tableIndex)) return false; if (!f.writeCall(callNode, Op::OldCallIndirect)) return false; // Call signature if (!f.encoder().writeVarU32(f.m().funcPtrTable(tableIndex).sigIndex())) return false; *type = Type::ret(ret); return true; } static bool CheckIsExternType(FunctionValidator& f, ParseNode* argNode, Type type) { if (!type.isExtern()) return f.failf(argNode, "%s is not a subtype of extern", type.toChars()); return true; } static bool CheckFFICall(FunctionValidator& f, ParseNode* callNode, unsigned ffiIndex, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); PropertyName* calleeName = CallCallee(callNode)->name(); if (ret.isFloat()) return f.fail(callNode, "FFI calls can't return float"); if (ret.isSimd()) return f.fail(callNode, "FFI calls can't return SIMD values"); ValTypeVector args; if (!CheckCallArgs(f, callNode, &args)) return false; Sig sig(Move(args), ret.canonicalToExprType()); uint32_t funcIndex; if (!f.m().declareImport(calleeName, Move(sig), ffiIndex, &funcIndex)) return false; if (!f.writeCall(callNode, Op::Call)) return false; if (!f.encoder().writeVarU32(funcIndex)) return false; *type = Type::ret(ret); return true; } static bool CheckFloatCoercionArg(FunctionValidator& f, ParseNode* inputNode, Type inputType) { if (inputType.isMaybeDouble()) return f.encoder().writeOp(Op::F32DemoteF64); if (inputType.isSigned()) return f.encoder().writeOp(Op::F32ConvertSI32); if (inputType.isUnsigned()) return f.encoder().writeOp(Op::F32ConvertUI32); if (inputType.isFloatish()) return true; return f.failf(inputNode, "%s is not a subtype of signed, unsigned, double? or floatish", inputType.toChars()); } static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type); static bool CheckCoercionArg(FunctionValidator& f, ParseNode* arg, Type expected, Type* type) { MOZ_ASSERT(expected.isCanonicalValType()); if (arg->isKind(PNK_CALL)) 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 if (expected.isSimd()) { if (!(argType <= expected)) return f.fail(arg, "argument to SIMD coercion isn't from the correct SIMD type"); } else { MOZ_CRASH("not call coercions"); } *type = Type::ret(expected); return true; } 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; } static bool CheckMathBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSMathBuiltinFunction func, Type* type) { unsigned arity = 0; Op f32; Op f64; 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; f64 = Op::F64Sin; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_cos: arity = 1; f64 = Op::F64Cos; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_tan: arity = 1; f64 = Op::F64Tan; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_asin: arity = 1; f64 = Op::F64Asin; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_acos: arity = 1; f64 = Op::F64Acos; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_atan: arity = 1; f64 = Op::F64Atan; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_exp: arity = 1; f64 = Op::F64Exp; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_log: arity = 1; f64 = Op::F64Log; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_pow: arity = 2; f64 = Op::F64Pow; f32 = Op::Unreachable; break; case AsmJSMathBuiltin_atan2: arity = 2; f64 = Op::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 (!f.encoder().writeOp(f64)) return false; } else { if (!f.encoder().writeOp(f32)) return false; } *type = opIsDouble ? Type::Double : Type::Floatish; return true; } namespace { // Include CheckSimdCallArgs in unnamed namespace to avoid MSVC name lookup bug. template static bool CheckSimdCallArgs(FunctionValidator& f, ParseNode* call, unsigned expectedArity, const CheckArgOp& checkArg) { unsigned numArgs = CallArgListLength(call); if (numArgs != expectedArity) return f.failf(call, "expected %u arguments to SIMD call, got %u", expectedArity, numArgs); ParseNode* arg = CallArgList(call); for (size_t i = 0; i < numArgs; i++, arg = NextNode(arg)) { MOZ_ASSERT(!!arg); Type argType; if (!CheckExpr(f, arg, &argType)) return false; if (!checkArg(f, arg, i, argType)) return false; } return true; } class CheckArgIsSubtypeOf { Type formalType_; public: explicit CheckArgIsSubtypeOf(SimdType t) : formalType_(t) {} bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const { if (!(actualType <= formalType_)) { return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(), formalType_.toChars()); } return true; } }; static inline Type SimdToCoercedScalarType(SimdType t) { switch (t) { case SimdType::Int8x16: case SimdType::Int16x8: case SimdType::Int32x4: case SimdType::Uint8x16: case SimdType::Uint16x8: case SimdType::Uint32x4: case SimdType::Bool8x16: case SimdType::Bool16x8: case SimdType::Bool32x4: return Type::Intish; case SimdType::Float32x4: return Type::Floatish; default: break; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unexpected SIMD type"); } class CheckSimdScalarArgs { SimdType simdType_; Type formalType_; public: explicit CheckSimdScalarArgs(SimdType simdType) : simdType_(simdType), formalType_(SimdToCoercedScalarType(simdType)) {} bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const { if (!(actualType <= formalType_)) { // As a special case, accept doublelit arguments to float32x4 ops by // re-emitting them as float32 constants. if (simdType_ != SimdType::Float32x4 || !actualType.isDoubleLit()) { return f.failf(arg, "%s is not a subtype of %s%s", actualType.toChars(), formalType_.toChars(), simdType_ == SimdType::Float32x4 ? " or doublelit" : ""); } // We emitted a double literal and actually want a float32. return f.encoder().writeOp(Op::F32DemoteF64); } return true; } }; class CheckSimdSelectArgs { Type formalType_; Type maskType_; public: explicit CheckSimdSelectArgs(SimdType t) : formalType_(t), maskType_(GetBooleanSimdType(t)) {} bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const { // The first argument is the boolean selector, the next two are the // values to choose from. Type wantedType = argIndex == 0 ? maskType_ : formalType_; if (!(actualType <= wantedType)) { return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(), wantedType.toChars()); } return true; } }; class CheckSimdVectorScalarArgs { SimdType formalSimdType_; public: explicit CheckSimdVectorScalarArgs(SimdType t) : formalSimdType_(t) {} bool operator()(FunctionValidator& f, ParseNode* arg, unsigned argIndex, Type actualType) const { MOZ_ASSERT(argIndex < 2); if (argIndex == 0) { // First argument is the vector if (!(actualType <= Type(formalSimdType_))) { return f.failf(arg, "%s is not a subtype of %s", actualType.toChars(), Type(formalSimdType_).toChars()); } return true; } // Second argument is the scalar return CheckSimdScalarArgs(formalSimdType_)(f, arg, argIndex, actualType); } }; } // namespace static bool CheckSimdUnary(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, Type* type) { if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType))) return false; if (!f.writeSimdOp(opType, op)) return false; *type = opType; return true; } static bool CheckSimdBinaryShift(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, Type *type) { if (!CheckSimdCallArgs(f, call, 2, CheckSimdVectorScalarArgs(opType))) return false; if (!f.writeSimdOp(opType, op)) return false; *type = opType; return true; } static bool CheckSimdBinaryComp(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, Type *type) { if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType))) return false; if (!f.writeSimdOp(opType, op)) return false; *type = GetBooleanSimdType(opType); return true; } static bool CheckSimdBinary(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, Type* type) { if (!CheckSimdCallArgs(f, call, 2, CheckArgIsSubtypeOf(opType))) return false; if (!f.writeSimdOp(opType, op)) return false; *type = opType; return true; } static bool CheckSimdExtractLane(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { switch (opType) { case SimdType::Int8x16: case SimdType::Int16x8: case SimdType::Int32x4: *type = Type::Signed; break; case SimdType::Uint8x16: case SimdType::Uint16x8: case SimdType::Uint32x4: *type = Type::Unsigned; break; case SimdType::Float32x4: *type = Type::Float; break; case SimdType::Bool8x16: case SimdType::Bool16x8: case SimdType::Bool32x4: *type = Type::Int; break; default: MOZ_CRASH("unhandled simd type"); } unsigned numArgs = CallArgListLength(call); if (numArgs != 2) return f.failf(call, "expected 2 arguments to SIMD extract, got %u", numArgs); ParseNode* arg = CallArgList(call); // First argument is the vector Type vecType; if (!CheckExpr(f, arg, &vecType)) return false; if (!(vecType <= Type(opType))) { return f.failf(arg, "%s is not a subtype of %s", vecType.toChars(), Type(opType).toChars()); } arg = NextNode(arg); // Second argument is the lane < vector length uint32_t lane; if (!IsLiteralOrConstInt(f, arg, &lane)) return f.failf(arg, "lane selector should be a constant integer literal"); if (lane >= GetSimdLanes(opType)) return f.failf(arg, "lane selector should be in bounds"); if (!f.writeSimdOp(opType, SimdOperation::Fn_extractLane)) return false; if (!f.encoder().writeVarU32(lane)) return false; return true; } static bool CheckSimdReplaceLane(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { unsigned numArgs = CallArgListLength(call); if (numArgs != 3) return f.failf(call, "expected 2 arguments to SIMD replace, got %u", numArgs); ParseNode* arg = CallArgList(call); // First argument is the vector Type vecType; if (!CheckExpr(f, arg, &vecType)) return false; if (!(vecType <= Type(opType))) { return f.failf(arg, "%s is not a subtype of %s", vecType.toChars(), Type(opType).toChars()); } arg = NextNode(arg); // Second argument is the lane < vector length uint32_t lane; if (!IsLiteralOrConstInt(f, arg, &lane)) return f.failf(arg, "lane selector should be a constant integer literal"); if (lane >= GetSimdLanes(opType)) return f.failf(arg, "lane selector should be in bounds"); arg = NextNode(arg); // Third argument is the scalar Type scalarType; if (!CheckExpr(f, arg, &scalarType)) return false; if (!(scalarType <= SimdToCoercedScalarType(opType))) { if (opType == SimdType::Float32x4 && scalarType.isDoubleLit()) { if (!f.encoder().writeOp(Op::F32DemoteF64)) return false; } else { return f.failf(arg, "%s is not the correct type to replace an element of %s", scalarType.toChars(), vecType.toChars()); } } if (!f.writeSimdOp(opType, SimdOperation::Fn_replaceLane)) return false; if (!f.encoder().writeVarU32(lane)) return false; *type = opType; return true; } typedef bool Bitcast; namespace { // Include CheckSimdCast in unnamed namespace to avoid MSVC name lookup bug (due to the use of Type). static bool CheckSimdCast(FunctionValidator& f, ParseNode* call, SimdType fromType, SimdType toType, SimdOperation op, Type* type) { if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(fromType))) return false; if (!f.writeSimdOp(toType, op)) return false; *type = toType; return true; } } // namespace static bool CheckSimdShuffleSelectors(FunctionValidator& f, ParseNode* lane, mozilla::Array& lanes, unsigned numLanes, unsigned maxLane) { for (unsigned i = 0; i < numLanes; i++, lane = NextNode(lane)) { uint32_t u32; if (!IsLiteralInt(f.m(), lane, &u32)) return f.failf(lane, "lane selector should be a constant integer literal"); if (u32 >= maxLane) return f.failf(lane, "lane selector should be less than %u", maxLane); lanes[i] = uint8_t(u32); } return true; } static bool CheckSimdSwizzle(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { const unsigned numLanes = GetSimdLanes(opType); unsigned numArgs = CallArgListLength(call); if (numArgs != 1 + numLanes) return f.failf(call, "expected %u arguments to SIMD swizzle, got %u", 1 + numLanes, numArgs); Type retType = opType; ParseNode* vec = CallArgList(call); Type vecType; if (!CheckExpr(f, vec, &vecType)) return false; if (!(vecType <= retType)) return f.failf(vec, "%s is not a subtype of %s", vecType.toChars(), retType.toChars()); if (!f.writeSimdOp(opType, SimdOperation::Fn_swizzle)) return false; mozilla::Array lanes; if (!CheckSimdShuffleSelectors(f, NextNode(vec), lanes, numLanes, numLanes)) return false; for (unsigned i = 0; i < numLanes; i++) { if (!f.encoder().writeFixedU8(lanes[i])) return false; } *type = retType; return true; } static bool CheckSimdShuffle(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { const unsigned numLanes = GetSimdLanes(opType); unsigned numArgs = CallArgListLength(call); if (numArgs != 2 + numLanes) return f.failf(call, "expected %u arguments to SIMD shuffle, got %u", 2 + numLanes, numArgs); Type retType = opType; ParseNode* arg = CallArgList(call); for (unsigned i = 0; i < 2; i++, arg = NextNode(arg)) { Type type; if (!CheckExpr(f, arg, &type)) return false; if (!(type <= retType)) return f.failf(arg, "%s is not a subtype of %s", type.toChars(), retType.toChars()); } if (!f.writeSimdOp(opType, SimdOperation::Fn_shuffle)) return false; mozilla::Array lanes; if (!CheckSimdShuffleSelectors(f, arg, lanes, numLanes, 2 * numLanes)) return false; for (unsigned i = 0; i < numLanes; i++) { if (!f.encoder().writeFixedU8(uint8_t(lanes[i]))) return false; } *type = retType; return true; } static bool CheckSimdLoadStoreArgs(FunctionValidator& f, ParseNode* call, Scalar::Type* viewType) { ParseNode* view = CallArgList(call); if (!view->isKind(PNK_NAME)) return f.fail(view, "expected Uint8Array view as SIMD.*.load/store first argument"); ParseNode* indexExpr = NextNode(view); if (!CheckAndPrepareArrayAccess(f, view, indexExpr, YesSimd, viewType)) return false; if (*viewType != Scalar::Uint8) return f.fail(view, "expected Uint8Array view as SIMD.*.load/store first argument"); return true; } static bool CheckSimdLoad(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, Type* type) { unsigned numArgs = CallArgListLength(call); if (numArgs != 2) return f.failf(call, "expected 2 arguments to SIMD load, got %u", numArgs); Scalar::Type viewType; if (!CheckSimdLoadStoreArgs(f, call, &viewType)) return false; if (!f.writeSimdOp(opType, op)) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; *type = opType; return true; } static bool CheckSimdStore(FunctionValidator& f, ParseNode* call, SimdType opType, SimdOperation op, Type* type) { unsigned numArgs = CallArgListLength(call); if (numArgs != 3) return f.failf(call, "expected 3 arguments to SIMD store, got %u", numArgs); Scalar::Type viewType; if (!CheckSimdLoadStoreArgs(f, call, &viewType)) return false; Type retType = opType; ParseNode* vecExpr = NextNode(NextNode(CallArgList(call))); Type vecType; if (!CheckExpr(f, vecExpr, &vecType)) return false; if (!f.writeSimdOp(opType, op)) return false; if (!WriteArrayAccessFlags(f, viewType)) return false; if (!(vecType <= retType)) return f.failf(vecExpr, "%s is not a subtype of %s", vecType.toChars(), retType.toChars()); *type = vecType; return true; } static bool CheckSimdSelect(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { if (!CheckSimdCallArgs(f, call, 3, CheckSimdSelectArgs(opType))) return false; if (!f.writeSimdOp(opType, SimdOperation::Fn_select)) return false; *type = opType; return true; } static bool CheckSimdAllTrue(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType))) return false; if (!f.writeSimdOp(opType, SimdOperation::Fn_allTrue)) return false; *type = Type::Int; return true; } static bool CheckSimdAnyTrue(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { if (!CheckSimdCallArgs(f, call, 1, CheckArgIsSubtypeOf(opType))) return false; if (!f.writeSimdOp(opType, SimdOperation::Fn_anyTrue)) return false; *type = Type::Int; return true; } static bool CheckSimdCheck(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { Type coerceTo; ParseNode* argNode; if (!IsCoercionCall(f.m(), call, &coerceTo, &argNode)) return f.failf(call, "expected 1 argument in call to check"); return CheckCoercionArg(f, argNode, coerceTo, type); } static bool CheckSimdSplat(FunctionValidator& f, ParseNode* call, SimdType opType, Type* type) { if (!CheckSimdCallArgs(f, call, 1, CheckSimdScalarArgs(opType))) return false; if (!f.writeSimdOp(opType, SimdOperation::Fn_splat)) return false; *type = opType; return true; } static bool CheckSimdOperationCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global, Type* type) { f.setUsesSimd(); MOZ_ASSERT(global->isSimdOperation()); SimdType opType = global->simdOperationType(); switch (SimdOperation op = global->simdOperation()) { case SimdOperation::Fn_check: return CheckSimdCheck(f, call, opType, type); #define _CASE(OP) case SimdOperation::Fn_##OP: FOREACH_SHIFT_SIMD_OP(_CASE) return CheckSimdBinaryShift(f, call, opType, op, type); FOREACH_COMP_SIMD_OP(_CASE) return CheckSimdBinaryComp(f, call, opType, op, type); FOREACH_NUMERIC_SIMD_BINOP(_CASE) FOREACH_FLOAT_SIMD_BINOP(_CASE) FOREACH_BITWISE_SIMD_BINOP(_CASE) FOREACH_SMINT_SIMD_BINOP(_CASE) return CheckSimdBinary(f, call, opType, op, type); #undef _CASE case SimdOperation::Fn_extractLane: return CheckSimdExtractLane(f, call, opType, type); case SimdOperation::Fn_replaceLane: return CheckSimdReplaceLane(f, call, opType, type); case SimdOperation::Fn_fromInt8x16Bits: return CheckSimdCast(f, call, SimdType::Int8x16, opType, op, type); case SimdOperation::Fn_fromUint8x16Bits: return CheckSimdCast(f, call, SimdType::Uint8x16, opType, op, type); case SimdOperation::Fn_fromInt16x8Bits: return CheckSimdCast(f, call, SimdType::Int16x8, opType, op, type); case SimdOperation::Fn_fromUint16x8Bits: return CheckSimdCast(f, call, SimdType::Uint16x8, opType, op, type); case SimdOperation::Fn_fromInt32x4: case SimdOperation::Fn_fromInt32x4Bits: return CheckSimdCast(f, call, SimdType::Int32x4, opType, op, type); case SimdOperation::Fn_fromUint32x4: case SimdOperation::Fn_fromUint32x4Bits: return CheckSimdCast(f, call, SimdType::Uint32x4, opType, op, type); case SimdOperation::Fn_fromFloat32x4: case SimdOperation::Fn_fromFloat32x4Bits: return CheckSimdCast(f, call, SimdType::Float32x4, opType, op, type); case SimdOperation::Fn_abs: case SimdOperation::Fn_neg: case SimdOperation::Fn_not: case SimdOperation::Fn_sqrt: case SimdOperation::Fn_reciprocalApproximation: case SimdOperation::Fn_reciprocalSqrtApproximation: return CheckSimdUnary(f, call, opType, op, type); case SimdOperation::Fn_swizzle: return CheckSimdSwizzle(f, call, opType, type); case SimdOperation::Fn_shuffle: return CheckSimdShuffle(f, call, opType, type); case SimdOperation::Fn_load: case SimdOperation::Fn_load1: case SimdOperation::Fn_load2: return CheckSimdLoad(f, call, opType, op, type); case SimdOperation::Fn_store: case SimdOperation::Fn_store1: case SimdOperation::Fn_store2: return CheckSimdStore(f, call, opType, op, type); case SimdOperation::Fn_select: return CheckSimdSelect(f, call, opType, type); case SimdOperation::Fn_splat: return CheckSimdSplat(f, call, opType, type); case SimdOperation::Fn_allTrue: return CheckSimdAllTrue(f, call, opType, type); case SimdOperation::Fn_anyTrue: return CheckSimdAnyTrue(f, call, opType, type); case SimdOperation::Fn_load3: case SimdOperation::Fn_store3: return f.fail(call, "asm.js does not support 3-element SIMD loads or stores"); case SimdOperation::Constructor: MOZ_CRASH("constructors are handled in CheckSimdCtorCall"); case SimdOperation::Fn_fromFloat64x2Bits: MOZ_CRASH("NYI"); } MOZ_CRASH("unexpected simd operation in CheckSimdOperationCall"); } static bool CheckSimdCtorCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global, Type* type) { f.setUsesSimd(); MOZ_ASSERT(call->isKind(PNK_CALL)); SimdType simdType = global->simdCtorType(); unsigned length = GetSimdLanes(simdType); if (!CheckSimdCallArgs(f, call, length, CheckSimdScalarArgs(simdType))) return false; if (!f.writeSimdOp(simdType, SimdOperation::Constructor)) return false; *type = simdType; return true; } static bool CheckUncoercedCall(FunctionValidator& f, ParseNode* expr, Type* type) { MOZ_ASSERT(expr->isKind(PNK_CALL)); const ModuleValidator::Global* global; if (IsCallToGlobal(f.m(), expr, &global)) { if (global->isMathFunction()) return CheckMathBuiltinCall(f, expr, global->mathBuiltinFunction(), type); if (global->isAtomicsFunction()) return CheckAtomicsBuiltinCall(f, expr, global->atomicsBuiltinFunction(), type); if (global->isSimdCtor()) return CheckSimdCtorCall(f, expr, global, type); if (global->isSimdOperation()) return CheckSimdOperationCall(f, expr, global, type); } return f.fail(expr, "all function calls must either be calls to standard lib math functions, " "standard atomic functions, standard SIMD constructors or operations, " "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(FunctionValidator& 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::F64ConvertSI32)) return false; } else if (actual.isUnsigned()) { if (!f.encoder().writeOp(Op::F64ConvertUI32)) return false; } else { return f.failf(expr, "%s is not a subtype of double?, float?, signed or unsigned", actual.toChars()); } break; default: MOZ_ASSERT(expected.isSimd(), "Incomplete switch"); if (actual != expected) return f.failf(expr, "got type %s, expected %s", actual.toChars(), expected.toChars()); break; } *type = Type::ret(expected); return true; } 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); } static bool CheckCoercedSimdCall(FunctionValidator& f, ParseNode* call, const ModuleValidator::Global* global, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); Type actual; if (global->isSimdCtor()) { if (!CheckSimdCtorCall(f, call, global, &actual)) return false; MOZ_ASSERT(actual.isSimd()); } else { MOZ_ASSERT(global->isSimdOperation()); if (!CheckSimdOperationCall(f, call, global, &actual)) return false; } return CoerceResult(f, call, ret, actual, type); } static bool CheckCoercedAtomicsBuiltinCall(FunctionValidator& f, ParseNode* callNode, AsmJSAtomicsBuiltinFunction func, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); Type actual; if (!CheckAtomicsBuiltinCall(f, callNode, func, &actual)) return false; return CoerceResult(f, callNode, ret, actual, type); } static bool CheckCoercedCall(FunctionValidator& f, ParseNode* call, Type ret, Type* type) { MOZ_ASSERT(ret.isCanonical()); JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); bool isSimd = false; if (IsNumericLiteral(f.m(), call, &isSimd)) { if (isSimd) f.setUsesSimd(); 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(PNK_ELEM)) return CheckFuncPtrCall(f, call, ret, type); if (!callee->isKind(PNK_NAME)) return f.fail(callee, "unexpected callee expression type"); PropertyName* calleeName = callee->name(); if (const ModuleValidator::Global* global = f.lookupGlobal(calleeName)) { switch (global->which()) { case ModuleValidator::Global::FFI: return CheckFFICall(f, call, global->ffiIndex(), ret, type); case ModuleValidator::Global::MathBuiltinFunction: return CheckCoercedMathBuiltinCall(f, call, global->mathBuiltinFunction(), ret, type); case ModuleValidator::Global::AtomicsBuiltinFunction: return CheckCoercedAtomicsBuiltinCall(f, call, global->atomicsBuiltinFunction(), ret, type); case ModuleValidator::Global::ConstantLiteral: case ModuleValidator::Global::ConstantImport: case ModuleValidator::Global::Variable: case ModuleValidator::Global::FuncPtrTable: case ModuleValidator::Global::ArrayView: case ModuleValidator::Global::ArrayViewCtor: return f.failName(callee, "'%s' is not callable function", callee->name()); case ModuleValidator::Global::SimdCtor: case ModuleValidator::Global::SimdOp: return CheckCoercedSimdCall(f, call, global, ret, type); case ModuleValidator::Global::Function: break; } } return CheckInternalCall(f, call, calleeName, ret, type); } static bool CheckPos(FunctionValidator& f, ParseNode* pos, Type* type) { MOZ_ASSERT(pos->isKind(PNK_POS)); ParseNode* operand = UnaryKid(pos); if (operand->isKind(PNK_CALL)) return CheckCoercedCall(f, operand, Type::Double, type); Type actual; if (!CheckExpr(f, operand, &actual)) return false; return CoerceResult(f, operand, Type::Double, actual, type); } static bool CheckNot(FunctionValidator& f, ParseNode* expr, Type* type) { MOZ_ASSERT(expr->isKind(PNK_NOT)); 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); } static bool CheckNeg(FunctionValidator& f, ParseNode* expr, Type* type) { MOZ_ASSERT(expr->isKind(PNK_NEG)); ParseNode* operand = UnaryKid(expr); Type operandType; if (!CheckExpr(f, operand, &operandType)) return false; if (operandType.isInt()) { *type = Type::Intish; return f.encoder().writeOp(Op::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()); } static bool CheckCoerceToInt(FunctionValidator& f, ParseNode* expr, Type* type) { MOZ_ASSERT(expr->isKind(PNK_BITNOT)); ParseNode* operand = UnaryKid(expr); Type operandType; if (!CheckExpr(f, operand, &operandType)) return false; if (operandType.isMaybeDouble() || operandType.isMaybeFloat()) { *type = Type::Signed; Op opcode = operandType.isMaybeDouble() ? Op::I32TruncSF64 : Op::I32TruncSF32; return f.encoder().writeOp(opcode); } if (!operandType.isIntish()) return f.failf(operand, "%s is not a subtype of double?, float? or intish", operandType.toChars()); *type = Type::Signed; return true; } static bool CheckBitNot(FunctionValidator& f, ParseNode* neg, Type* type) { MOZ_ASSERT(neg->isKind(PNK_BITNOT)); ParseNode* operand = UnaryKid(neg); if (operand->isKind(PNK_BITNOT)) 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(Op::I32BitNot)) return false; *type = Type::Signed; return true; } static bool CheckAsExprStatement(FunctionValidator& f, ParseNode* exprStmt); static bool CheckComma(FunctionValidator& f, ParseNode* comma, Type* type) { MOZ_ASSERT(comma->isKind(PNK_COMMA)); 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); } static bool CheckConditional(FunctionValidator& f, ParseNode* ternary, Type* type) { MOZ_ASSERT(ternary->isKind(PNK_CONDITIONAL)); 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 if (thenType.isSimd() && elseType == thenType) { *type = thenType; } else { return f.failf(ternary, "then/else branches of conditional must both produce int, float, " "double or SIMD types, current types are %s and %s", thenType.toChars(), elseType.toChars()); } if (!f.popIf(typeAt, type->toWasmBlockSignatureType())) return false; return true; } 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()) < (1<<20)) return true; return false; case NumLit::BigUnsigned: case NumLit::Double: case NumLit::Float: case NumLit::OutOfRangeInt: case NumLit::Int8x16: case NumLit::Uint8x16: case NumLit::Int16x8: case NumLit::Uint16x8: case NumLit::Int32x4: case NumLit::Uint32x4: case NumLit::Float32x4: case NumLit::Bool8x16: case NumLit::Bool16x8: case NumLit::Bool32x4: return false; } MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad literal"); } static bool CheckMultiply(FunctionValidator& f, ParseNode* star, Type* type) { MOZ_ASSERT(star->isKind(PNK_STAR)); 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?"); } static bool CheckAddOrSub(FunctionValidator& f, ParseNode* expr, Type* type, unsigned* numAddOrSubOut = nullptr) { JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); MOZ_ASSERT(expr->isKind(PNK_ADD) || expr->isKind(PNK_SUB)); ParseNode* lhs = AddSubLeft(expr); ParseNode* rhs = AddSubRight(expr); Type lhsType, rhsType; unsigned lhsNumAddOrSub, rhsNumAddOrSub; if (lhs->isKind(PNK_ADD) || lhs->isKind(PNK_SUB)) { 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(PNK_ADD) || rhs->isKind(PNK_SUB)) { 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(PNK_ADD) ? Op::I32Add : Op::I32Sub)) return false; *type = Type::Intish; } else if (lhsType.isMaybeDouble() && rhsType.isMaybeDouble()) { if (!f.encoder().writeOp(expr->isKind(PNK_ADD) ? Op::F64Add : Op::F64Sub)) return false; *type = Type::Double; } else if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) { if (!f.encoder().writeOp(expr->isKind(PNK_ADD) ? 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; } static bool CheckDivOrMod(FunctionValidator& f, ParseNode* expr, Type* type) { MOZ_ASSERT(expr->isKind(PNK_DIV) || expr->isKind(PNK_MOD)); 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; return f.encoder().writeOp(expr->isKind(PNK_DIV) ? Op::F64Div : Op::F64Mod); } if (lhsType.isMaybeFloat() && rhsType.isMaybeFloat()) { *type = Type::Floatish; if (expr->isKind(PNK_DIV)) return f.encoder().writeOp(Op::F32Div); else return f.fail(expr, "modulo cannot receive float arguments"); } if (lhsType.isSigned() && rhsType.isSigned()) { *type = Type::Intish; return f.encoder().writeOp(expr->isKind(PNK_DIV) ? Op::I32DivS : Op::I32RemS); } if (lhsType.isUnsigned() && rhsType.isUnsigned()) { *type = Type::Intish; return f.encoder().writeOp(expr->isKind(PNK_DIV) ? 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()); } static bool CheckComparison(FunctionValidator& f, ParseNode* comp, Type* type) { MOZ_ASSERT(comp->isKind(PNK_LT) || comp->isKind(PNK_LE) || comp->isKind(PNK_GT) || comp->isKind(PNK_GE) || comp->isKind(PNK_EQ) || comp->isKind(PNK_NE)); 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->getOp()) { case JSOP_EQ: stmt = Op::I32Eq; break; case JSOP_NE: stmt = Op::I32Ne; break; case JSOP_LT: stmt = Op::I32LtS; break; case JSOP_LE: stmt = Op::I32LeS; break; case JSOP_GT: stmt = Op::I32GtS; break; case JSOP_GE: stmt = Op::I32GeS; break; default: MOZ_CRASH("unexpected comparison op"); } } else if (lhsType.isUnsigned() && rhsType.isUnsigned()) { switch (comp->getOp()) { case JSOP_EQ: stmt = Op::I32Eq; break; case JSOP_NE: stmt = Op::I32Ne; break; case JSOP_LT: stmt = Op::I32LtU; break; case JSOP_LE: stmt = Op::I32LeU; break; case JSOP_GT: stmt = Op::I32GtU; break; case JSOP_GE: stmt = Op::I32GeU; break; default: MOZ_CRASH("unexpected comparison op"); } } else if (lhsType.isDouble()) { switch (comp->getOp()) { case JSOP_EQ: stmt = Op::F64Eq; break; case JSOP_NE: stmt = Op::F64Ne; break; case JSOP_LT: stmt = Op::F64Lt; break; case JSOP_LE: stmt = Op::F64Le; break; case JSOP_GT: stmt = Op::F64Gt; break; case JSOP_GE: stmt = Op::F64Ge; break; default: MOZ_CRASH("unexpected comparison op"); } } else if (lhsType.isFloat()) { switch (comp->getOp()) { case JSOP_EQ: stmt = Op::F32Eq; break; case JSOP_NE: stmt = Op::F32Ne; break; case JSOP_LT: stmt = Op::F32Lt; break; case JSOP_LE: stmt = Op::F32Le; break; case JSOP_GT: stmt = Op::F32Gt; break; case JSOP_GE: stmt = Op::F32Ge; break; default: MOZ_CRASH("unexpected comparison op"); } } else { MOZ_CRASH("unexpected type"); } *type = Type::Int; return f.encoder().writeOp(stmt); } 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 PNK_BITOR: identityElement = 0; onlyOnRight = false; *type = Type::Signed; break; case PNK_BITAND: identityElement = -1; onlyOnRight = false; *type = Type::Signed; break; case PNK_BITXOR: identityElement = 0; onlyOnRight = false; *type = Type::Signed; break; case PNK_LSH: identityElement = 0; onlyOnRight = true; *type = Type::Signed; break; case PNK_RSH: identityElement = 0; onlyOnRight = true; *type = Type::Signed; break; case PNK_URSH: 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(PNK_BITOR) && lhs->isKind(PNK_CALL)) 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 PNK_BITOR: if (!f.encoder().writeOp(Op::I32Or)) return false; break; case PNK_BITAND: if (!f.encoder().writeOp(Op::I32And)) return false; break; case PNK_BITXOR: if (!f.encoder().writeOp(Op::I32Xor)) return false; break; case PNK_LSH: if (!f.encoder().writeOp(Op::I32Shl)) return false; break; case PNK_RSH: if (!f.encoder().writeOp(Op::I32ShrS)) return false; break; case PNK_URSH: if (!f.encoder().writeOp(Op::I32ShrU)) return false; break; default: MOZ_CRASH("not a bitwise op"); } return true; } static bool CheckExpr(FunctionValidator& f, ParseNode* expr, Type* type) { JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); bool isSimd = false; if (IsNumericLiteral(f.m(), expr, &isSimd)) { if (isSimd) f.setUsesSimd(); return CheckNumericLiteral(f, expr, type); } switch (expr->getKind()) { case PNK_NAME: return CheckVarRef(f, expr, type); case PNK_ELEM: return CheckLoadArray(f, expr, type); case PNK_ASSIGN: return CheckAssign(f, expr, type); case PNK_POS: return CheckPos(f, expr, type); case PNK_NOT: return CheckNot(f, expr, type); case PNK_NEG: return CheckNeg(f, expr, type); case PNK_BITNOT: return CheckBitNot(f, expr, type); case PNK_COMMA: return CheckComma(f, expr, type); case PNK_CONDITIONAL: return CheckConditional(f, expr, type); case PNK_STAR: return CheckMultiply(f, expr, type); case PNK_CALL: return CheckUncoercedCall(f, expr, type); case PNK_ADD: case PNK_SUB: return CheckAddOrSub(f, expr, type); case PNK_DIV: case PNK_MOD: return CheckDivOrMod(f, expr, type); case PNK_LT: case PNK_LE: case PNK_GT: case PNK_GE: case PNK_EQ: case PNK_NE: return CheckComparison(f, expr, type); case PNK_BITOR: case PNK_BITAND: case PNK_BITXOR: case PNK_LSH: case PNK_RSH: case PNK_URSH: return CheckBitwise(f, expr, type); default:; } return f.fail(expr, "unsupported expression"); } static bool CheckStatement(FunctionValidator& f, ParseNode* stmt); static bool CheckAsExprStatement(FunctionValidator& f, ParseNode* expr) { if (expr->isKind(PNK_CALL)) { 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; } static bool CheckExprStatement(FunctionValidator& f, ParseNode* exprStmt) { MOZ_ASSERT(exprStmt->isKind(PNK_SEMI)); ParseNode* expr = UnaryKid(exprStmt); if (!expr) return true; return CheckAsExprStatement(f, expr); } 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()); // TODO change this to i32.eqz // i32.eq 0 $f if (!f.writeInt32Lit(0)) return false; if (!f.encoder().writeOp(Op::I32Eq)) return false; // brIf (i32.eq 0 $f) $out if (!f.writeBreakIf()) return false; return true; } static bool CheckWhile(FunctionValidator& f, ParseNode* whileStmt, const NameVector* labels = nullptr) { MOZ_ASSERT(whileStmt->isKind(PNK_WHILE)); 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; } static bool CheckFor(FunctionValidator& f, ParseNode* forStmt, const NameVector* labels = nullptr) { MOZ_ASSERT(forStmt->isKind(PNK_FOR)); ParseNode* forHead = BinaryLeft(forStmt); ParseNode* body = BinaryRight(forStmt); if (!forHead->isKind(PNK_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; } static bool CheckDoWhile(FunctionValidator& f, ParseNode* whileStmt, const NameVector* labels = nullptr) { MOZ_ASSERT(whileStmt->isKind(PNK_DOWHILE)); 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; } static bool CheckStatementList(FunctionValidator& f, ParseNode*, const NameVector* = nullptr); static bool CheckLabel(FunctionValidator& f, ParseNode* labeledStmt) { MOZ_ASSERT(labeledStmt->isKind(PNK_LABEL)); NameVector labels; ParseNode* innermost = labeledStmt; do { if (!labels.append(LabeledStatementLabel(innermost))) return false; innermost = LabeledStatementStatement(innermost); } while (innermost->getKind() == PNK_LABEL); switch (innermost->getKind()) { case PNK_FOR: return CheckFor(f, innermost, &labels); case PNK_DOWHILE: return CheckDoWhile(f, innermost, &labels); case PNK_WHILE: return CheckWhile(f, innermost, &labels); case PNK_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; } static bool CheckIf(FunctionValidator& f, ParseNode* ifStmt) { uint32_t numIfEnd = 1; recurse: MOZ_ASSERT(ifStmt->isKind(PNK_IF)); ParseNode* cond = TernaryKid1(ifStmt); ParseNode* thenStmt = TernaryKid2(ifStmt); ParseNode* elseStmt = TernaryKid3(ifStmt); Type condType; if (!CheckExpr(f, cond, &condType)) return false; if (!condType.isInt()) return f.failf(cond, "%s is not a subtype of int", condType.toChars()); size_t typeAt; if (!f.pushIf(&typeAt)) return false; f.setIfType(typeAt, ExprType::Void); if (!CheckStatement(f, thenStmt)) return false; if (elseStmt) { if (!f.switchToElse()) return false; if (elseStmt->isKind(PNK_IF)) { 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(FunctionValidator& 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: case NumLit::Int8x16: case NumLit::Uint8x16: case NumLit::Int16x8: case NumLit::Uint16x8: case NumLit::Int32x4: case NumLit::Uint32x4: case NumLit::Float32x4: case NumLit::Bool8x16: case NumLit::Bool16x8: case NumLit::Bool32x4: return f.fail(caseExpr, "switch case expression must be an integer literal"); } return true; } static bool CheckDefaultAtEnd(FunctionValidator& 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(FunctionValidator& f, ParseNode* stmt, int32_t* low, int32_t* high, uint32_t* tableLength) { if (IsDefaultCase(stmt)) { *low = 0; *high = -1; *tableLength = 0; return true; } int32_t i = 0; if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) return false; *low = *high = i; ParseNode* initialStmt = stmt; for (stmt = NextNode(stmt); stmt && !IsDefaultCase(stmt); stmt = NextNode(stmt)) { int32_t i = 0; if (!CheckCaseExpr(f, CaseExpr(stmt), &i)) return false; *low = Min(*low, i); *high = Max(*high, i); } int64_t i64 = (int64_t(*high) - int64_t(*low)) + 1; if (i64 > MaxBrTableElems) return f.fail(initialStmt, "all switch statements generate tables; this table would be too big"); *tableLength = uint32_t(i64); return true; } 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. static bool CheckSwitch(FunctionValidator& f, ParseNode* switchStmt) { MOZ_ASSERT(switchStmt->isKind(PNK_SWITCH)); ParseNode* switchExpr = BinaryLeft(switchStmt); ParseNode* switchBody = BinaryRight(switchStmt); if (switchBody->isKind(PNK_LEXICALSCOPE)) { if (!switchBody->isEmptyScope()) return f.fail(switchBody, "switch body may not contain lexical declarations"); switchBody = switchBody->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. if (!f.popBreakableBlock()) return false; return true; } static bool CheckReturnType(FunctionValidator& f, ParseNode* usepn, Type ret) { if (!f.hasAlreadyReturned()) { f.setReturnedType(ret.canonicalToExprType()); return true; } if (f.returnedType() != ret.canonicalToExprType()) { return f.failf(usepn, "%s incompatible with previous return of type %s", Type::ret(ret).toChars(), ToCString(f.returnedType())); } return true; } 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; } if (!f.encoder().writeOp(Op::Return)) return false; return true; } static bool CheckStatementList(FunctionValidator& f, ParseNode* stmtList, const NameVector* labels /*= nullptr */) { MOZ_ASSERT(stmtList->isKind(PNK_STATEMENTLIST)); if (!f.pushUnbreakableBlock(labels)) return false; for (ParseNode* stmt = ListHead(stmtList); stmt; stmt = NextNode(stmt)) { if (!CheckStatement(f, stmt)) return false; } if (!f.popUnbreakableBlock(labels)) return false; return true; } static bool CheckLexicalScope(FunctionValidator& f, ParseNode* lexicalScope) { MOZ_ASSERT(lexicalScope->isKind(PNK_LEXICALSCOPE)); if (!lexicalScope->isEmptyScope()) return f.fail(lexicalScope, "cannot have 'let' or 'const' declarations"); return CheckStatement(f, lexicalScope->scopeBody()); } static bool CheckBreakOrContinue(FunctionValidator& f, bool isBreak, ParseNode* stmt) { if (PropertyName* maybeLabel = LoopControlMaybeLabel(stmt)) return f.writeLabeledBreakOrContinue(maybeLabel, isBreak); return f.writeUnlabeledBreakOrContinue(isBreak); } static bool CheckStatement(FunctionValidator& f, ParseNode* stmt) { JS_CHECK_RECURSION_DONT_REPORT(f.cx(), return f.m().failOverRecursed()); switch (stmt->getKind()) { case PNK_SEMI: return CheckExprStatement(f, stmt); case PNK_WHILE: return CheckWhile(f, stmt); case PNK_FOR: return CheckFor(f, stmt); case PNK_DOWHILE: return CheckDoWhile(f, stmt); case PNK_LABEL: return CheckLabel(f, stmt); case PNK_IF: return CheckIf(f, stmt); case PNK_SWITCH: return CheckSwitch(f, stmt); case PNK_RETURN: return CheckReturn(f, stmt); case PNK_STATEMENTLIST: return CheckStatementList(f, stmt); case PNK_BREAK: return CheckBreakOrContinue(f, true, stmt); case PNK_CONTINUE: return CheckBreakOrContinue(f, false, stmt); case PNK_LEXICALSCOPE: return CheckLexicalScope(f, stmt); default:; } return f.fail(stmt, "unexpected statement kind"); } static bool ParseFunction(ModuleValidator& m, ParseNode** fnOut, unsigned* line) { TokenStream& tokenStream = m.tokenStream(); tokenStream.consumeKnownToken(TOK_FUNCTION, TokenStream::Operand); uint32_t toStringStart = tokenStream.currentToken().pos.begin; *line = tokenStream.srcCoords.lineNum(tokenStream.currentToken().pos.end); TokenKind tk; if (!tokenStream.getToken(&tk, TokenStream::Operand)) return false; if (!TokenKindIsPossibleIdentifier(tk)) return false; // The regular parser will throw a SyntaxError, no need to m.fail. RootedPropertyName name(m.cx(), m.parser().bindingIdentifier(YieldIsName)); if (!name) return false; ParseNode* fn = m.parser().handler.newFunctionStatement(); if (!fn) return false; RootedFunction& fun = m.dummyFunction(); fun->setAtom(name); fun->setArgCount(0); ParseContext* outerpc = m.parser().pc; Directives directives(outerpc); FunctionBox* funbox = m.parser().newFunctionBox(fn, fun, toStringStart, directives, NotGenerator, SyncFunction, /* tryAnnexB = */ false); if (!funbox) return false; funbox->initWithEnclosingParseContext(outerpc, frontend::Statement); Directives newDirectives = directives; ParseContext funpc(&m.parser(), funbox, &newDirectives); if (!funpc.init()) return false; if (!m.parser().functionFormalParametersAndBody(InAllowed, YieldIsName, fn, Statement)) { if (tokenStream.hadError() || directives == newDirectives) return false; return m.fail(fn, "encountered new directive in function"); } MOZ_ASSERT(!tokenStream.hadError()); MOZ_ASSERT(directives == newDirectives); *fnOut = fn; return true; } 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. AsmJSParser::Mark mark = m.parser().mark(); ParseNode* fn = nullptr; unsigned line = 0; if (!ParseFunction(m, &fn, &line)) return false; if (!CheckFunctionHead(m, fn)) return false; FunctionValidator f(m, fn); if (!f.init(FunctionName(fn), line)) return m.fail(fn, "internal compiler failure (probably out of memory)"); ParseNode* stmtIter = ListHead(FunctionStatementList(fn)); 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; ModuleValidator::Func* func = nullptr; if (!CheckFunctionSignature(m, fn, Sig(Move(args), f.returnedType()), FunctionName(fn), &func)) return false; if (func->defined()) return m.failName(fn, "function '%s' already defined", FunctionName(fn)); func->define(fn); if (!f.finish(func->index())) return m.fail(fn, "internal compiler failure (probably out of memory)"); // Release the parser's lifo memory only after the last use of a parse node. m.parser().release(mark); return true; } static bool CheckAllFunctionsDefined(ModuleValidator& m) { for (unsigned i = 0; i < m.numFunctions(); i++) { ModuleValidator::Func& f = m.function(i); if (!f.defined()) return m.failNameOffset(f.firstUse(), "missing definition of function %s", f.name()); } return true; } static bool CheckFunctions(ModuleValidator& m) { while (true) { TokenKind tk; if (!PeekToken(m.parser(), &tk)) return false; if (tk != TOK_FUNCTION) break; if (!CheckFunction(m)) return false; } return CheckAllFunctionsDefined(m); } static bool CheckFuncPtrTable(ModuleValidator& m, ParseNode* var) { if (!var->isKind(PNK_NAME)) return m.fail(var, "function-pointer table name is not a plain name"); ParseNode* arrayLiteral = MaybeInitializer(var); if (!arrayLiteral || !arrayLiteral->isKind(PNK_ARRAY)) 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 elemFuncIndices; const Sig* sig = nullptr; for (ParseNode* elem = ListHead(arrayLiteral); elem; elem = NextNode(elem)) { if (!elem->isKind(PNK_NAME)) return m.fail(elem, "function-pointer table's elements must be names of functions"); PropertyName* funcName = elem->name(); const ModuleValidator::Func* func = m.lookupFunction(funcName); if (!func) return m.fail(elem, "function-pointer table's elements must be names of functions"); const Sig& funcSig = m.mg().funcSig(func->index()); if (sig) { if (*sig != funcSig) return m.fail(elem, "all functions in table must have same signature"); } else { sig = &funcSig; } if (!elemFuncIndices.append(func->index())) return false; } Sig copy; if (!copy.clone(*sig)) return false; uint32_t tableIndex; if (!CheckFuncPtrTableAgainstExisting(m, var, var->name(), Move(copy), mask, &tableIndex)) return false; if (!m.defineFuncPtrTable(tableIndex, Move(elemFuncIndices))) return m.fail(var, "duplicate function-pointer definition"); return true; } 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++) { ModuleValidator::FuncPtrTable& funcPtrTable = m.funcPtrTable(i); if (!funcPtrTable.defined()) { return m.failNameOffset(funcPtrTable.firstUse(), "function-pointer table %s wasn't defined", funcPtrTable.name()); } } return true; } static bool CheckModuleExportFunction(ModuleValidator& m, ParseNode* pn, PropertyName* maybeFieldName = nullptr) { if (!pn->isKind(PNK_NAME)) return m.fail(pn, "expected name of exported function"); PropertyName* funcName = pn->name(); const ModuleValidator::Func* func = m.lookupFunction(funcName); if (!func) return m.failName(pn, "function '%s' not found", funcName); return m.addExportField(pn, *func, maybeFieldName); } static bool CheckModuleExportObject(ModuleValidator& m, ParseNode* object) { MOZ_ASSERT(object->isKind(PNK_OBJECT)); for (ParseNode* pn = ListHead(object); pn; pn = NextNode(pn)) { if (!IsNormalObjectField(m.cx(), pn)) return m.fail(pn, "only normal object properties may be used in the export object literal"); PropertyName* fieldName = ObjectNormalFieldName(m.cx(), pn); ParseNode* initNode = ObjectNormalFieldInitializer(m.cx(), pn); if (!initNode->isKind(PNK_NAME)) return m.fail(initNode, "initializer of exported object literal must be name of function"); if (!CheckModuleExportFunction(m, initNode, fieldName)) return false; } return true; } static bool CheckModuleReturn(ModuleValidator& m) { TokenKind tk; if (!GetToken(m.parser(), &tk)) return false; TokenStream& ts = m.parser().tokenStream; if (tk != TOK_RETURN) { return m.failCurrentOffset((tk == TOK_RC || tk == TOK_EOF) ? "expecting return statement" : "invalid asm.js. statement"); } ts.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(PNK_OBJECT)) { if (!CheckModuleExportObject(m, returnExpr)) return false; } else { if (!CheckModuleExportFunction(m, returnExpr)) return false; } return true; } static bool CheckModuleEnd(ModuleValidator &m) { TokenKind tk; if (!GetToken(m.parser(), &tk)) return false; if (tk != TOK_EOF && tk != TOK_RC) return m.failCurrentOffset("top-level export (return) must be the last statement"); m.parser().tokenStream.ungetToken(); return true; } static SharedModule CheckModule(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList, unsigned* time) { int64_t before = PRMJ_Now(); ParseNode* moduleFunctionNode = parser.pc->functionBox()->functionNode; MOZ_ASSERT(moduleFunctionNode); ModuleValidator m(cx, parser, moduleFunctionNode); if (!m.init()) return nullptr; if (!CheckFunctionHead(m, moduleFunctionNode)) return nullptr; if (!CheckModuleArguments(m, moduleFunctionNode)) return nullptr; if (!CheckPrecedingStatements(m, stmtList)) return nullptr; if (!CheckModuleProcessingDirectives(m)) return nullptr; if (!CheckModuleGlobals(m)) return nullptr; if (!m.startFunctionBodies()) return nullptr; if (!CheckFunctions(m)) return nullptr; if (!m.finishFunctionBodies()) 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) { JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr, JSMSG_USE_ASM_LINK_FAIL, str); return false; } static bool IsMaybeWrappedScriptedProxy(JSObject* obj) { JSObject* unwrapped = UncheckedUnwrap(obj); return unwrapped && IsScriptedProxy(unwrapped); } static bool GetDataProperty(JSContext* cx, HandleValue objVal, HandleAtom field, MutableHandleValue v) { if (!objVal.isObject()) return LinkFail(cx, "accessing property of non-object"); RootedObject obj(cx, &objVal.toObject()); if (IsMaybeWrappedScriptedProxy(obj)) return LinkFail(cx, "accessing property of a Proxy"); Rooted desc(cx); RootedId id(cx, AtomToId(field)); if (!GetPropertyDescriptor(cx, obj, id, &desc)) return false; if (!desc.object()) return LinkFail(cx, "property not present on object"); if (!desc.isDataDescriptor()) return LinkFail(cx, "property is not a data property"); v.set(desc.value()); return true; } static bool GetDataProperty(JSContext* cx, HandleValue objVal, const char* fieldChars, MutableHandleValue v) { RootedAtom field(cx, AtomizeUTF8Chars(cx, fieldChars, strlen(fieldChars))); if (!field) return false; return GetDataProperty(cx, objVal, field, v); } static bool GetDataProperty(JSContext* cx, HandleValue objVal, ImmutablePropertyNamePtr field, MutableHandleValue v) { // Help the conversion along for all the cx->names().* users. HandlePropertyName fieldHandle = field; return GetDataProperty(cx, objVal, fieldHandle, v); } static bool HasObjectValueOfMethodPure(JSObject* obj, JSContext* cx) { Value v; if (!GetPropertyPure(cx, obj, NameToId(cx->names().valueOf), &v)) return false; JSFunction* fun; if (!IsFunctionObject(v, &fun)) return false; return IsSelfHostedFunctionWithName(fun, cx->names().Object_valueOf); } static bool HasPureCoercion(JSContext* cx, HandleValue v) { // Unsigned SIMD types are not allowed in function signatures. if (IsVectorObject(v) || IsVectorObject(v) || IsVectorObject(v)) return true; // Ideally, we'd reject all non-SIMD 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. if (v.toObject().is() && HasNoToPrimitiveMethodPure(&v.toObject(), cx) && HasObjectValueOfMethodPure(&v.toObject(), cx) && HasNativeMethodPure(&v.toObject(), cx->names().toString, fun_toString, cx)) { return true; } return false; } static bool ValidateGlobalVariable(JSContext* cx, const AsmJSGlobal& global, HandleValue importVal, Val* val) { switch (global.varInitKind()) { case AsmJSGlobal::InitConstant: *val = 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()) { case ValType::I32: { int32_t i32; if (!ToInt32(cx, v, &i32)) return false; *val = Val(uint32_t(i32)); return true; } case ValType::I64: MOZ_CRASH("int64"); case ValType::F32: { float f; if (!RoundFloat32(cx, v, &f)) return false; *val = Val(RawF32(f)); return true; } case ValType::F64: { double d; if (!ToNumber(cx, v, &d)) return false; *val = Val(RawF64(d)); return true; } case ValType::I8x16: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; *val = Val(simdConstant.asInt8x16()); return true; } case ValType::I16x8: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; *val = Val(simdConstant.asInt16x8()); return true; } case ValType::I32x4: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; *val = Val(simdConstant.asInt32x4()); return true; } case ValType::F32x4: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; *val = Val(simdConstant.asFloat32x4()); return true; } case ValType::B8x16: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; // Bool8x16 uses the same data layout as Int8x16. *val = Val(simdConstant.asInt8x16()); return true; } case ValType::B16x8: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; // Bool16x8 uses the same data layout as Int16x8. *val = Val(simdConstant.asInt16x8()); return true; } case ValType::B32x4: { SimdConstant simdConstant; if (!ToSimdConstant(cx, v, &simdConstant)) return false; // Bool32x4 uses the same data layout as Int32x4. *val = Val(simdConstant.asInt32x4()); return true; } } } } 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; RootedValue v(cx); if (!GetDataProperty(cx, globalVal, global.field(), &v)) return false; bool tac = IsTypedArrayConstructor(v, global.viewType()); if (!tac) return LinkFail(cx, "bad typed array constructor"); return true; } static bool ValidateMathBuiltinFunction(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { RootedValue v(cx); if (!GetDataProperty(cx, globalVal, cx->names().Math, &v)) return false; if (!GetDataProperty(cx, v, global.field(), &v)) return false; Native native = nullptr; switch (global.mathBuiltinFunction()) { case AsmJSMathBuiltin_sin: native = math_sin; break; case AsmJSMathBuiltin_cos: native = math_cos; break; case AsmJSMathBuiltin_tan: native = math_tan; break; case AsmJSMathBuiltin_asin: native = math_asin; break; case AsmJSMathBuiltin_acos: native = math_acos; break; case AsmJSMathBuiltin_atan: native = math_atan; break; case AsmJSMathBuiltin_ceil: native = math_ceil; break; case AsmJSMathBuiltin_floor: native = math_floor; break; case AsmJSMathBuiltin_exp: native = math_exp; break; case AsmJSMathBuiltin_log: native = math_log; break; case AsmJSMathBuiltin_pow: native = math_pow; break; case AsmJSMathBuiltin_sqrt: native = math_sqrt; break; case AsmJSMathBuiltin_min: native = math_min; break; case AsmJSMathBuiltin_max: native = math_max; break; case AsmJSMathBuiltin_abs: native = math_abs; break; case AsmJSMathBuiltin_atan2: native = math_atan2; break; case AsmJSMathBuiltin_imul: native = math_imul; break; case AsmJSMathBuiltin_clz32: native = math_clz32; break; case AsmJSMathBuiltin_fround: native = math_fround; break; } if (!IsNativeFunction(v, native)) return LinkFail(cx, "bad Math.* builtin function"); return true; } static bool ValidateSimdType(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal, MutableHandleValue out) { RootedValue v(cx); if (!GetDataProperty(cx, globalVal, cx->names().SIMD, &v)) return false; SimdType type; if (global.which() == AsmJSGlobal::SimdCtor) type = global.simdCtorType(); else type = global.simdOperationType(); RootedPropertyName simdTypeName(cx, SimdTypeToName(cx->names(), type)); if (!GetDataProperty(cx, v, simdTypeName, &v)) return false; if (!v.isObject()) return LinkFail(cx, "bad SIMD type"); RootedObject simdDesc(cx, &v.toObject()); if (!simdDesc->is()) return LinkFail(cx, "bad SIMD type"); if (type != simdDesc->as().type()) return LinkFail(cx, "bad SIMD type"); out.set(v); return true; } static bool ValidateSimdType(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { RootedValue _(cx); return ValidateSimdType(cx, global, globalVal, &_); } static bool ValidateSimdOperation(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { RootedValue v(cx); JS_ALWAYS_TRUE(ValidateSimdType(cx, global, globalVal, &v)); if (!GetDataProperty(cx, v, global.field(), &v)) return false; Native native = nullptr; switch (global.simdOperationType()) { #define SET_NATIVE_INT8X16(op) case SimdOperation::Fn_##op: native = simd_int8x16_##op; break; #define SET_NATIVE_INT16X8(op) case SimdOperation::Fn_##op: native = simd_int16x8_##op; break; #define SET_NATIVE_INT32X4(op) case SimdOperation::Fn_##op: native = simd_int32x4_##op; break; #define SET_NATIVE_UINT8X16(op) case SimdOperation::Fn_##op: native = simd_uint8x16_##op; break; #define SET_NATIVE_UINT16X8(op) case SimdOperation::Fn_##op: native = simd_uint16x8_##op; break; #define SET_NATIVE_UINT32X4(op) case SimdOperation::Fn_##op: native = simd_uint32x4_##op; break; #define SET_NATIVE_FLOAT32X4(op) case SimdOperation::Fn_##op: native = simd_float32x4_##op; break; #define SET_NATIVE_BOOL8X16(op) case SimdOperation::Fn_##op: native = simd_bool8x16_##op; break; #define SET_NATIVE_BOOL16X8(op) case SimdOperation::Fn_##op: native = simd_bool16x8_##op; break; #define SET_NATIVE_BOOL32X4(op) case SimdOperation::Fn_##op: native = simd_bool32x4_##op; break; #define FALLTHROUGH(op) case SimdOperation::Fn_##op: case SimdType::Int8x16: switch (global.simdOperation()) { FORALL_INT8X16_ASMJS_OP(SET_NATIVE_INT8X16) SET_NATIVE_INT8X16(fromUint8x16Bits) SET_NATIVE_INT8X16(fromUint16x8Bits) SET_NATIVE_INT8X16(fromUint32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Int16x8: switch (global.simdOperation()) { FORALL_INT16X8_ASMJS_OP(SET_NATIVE_INT16X8) SET_NATIVE_INT16X8(fromUint8x16Bits) SET_NATIVE_INT16X8(fromUint16x8Bits) SET_NATIVE_INT16X8(fromUint32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Int32x4: switch (global.simdOperation()) { FORALL_INT32X4_ASMJS_OP(SET_NATIVE_INT32X4) SET_NATIVE_INT32X4(fromUint8x16Bits) SET_NATIVE_INT32X4(fromUint16x8Bits) SET_NATIVE_INT32X4(fromUint32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Uint8x16: switch (global.simdOperation()) { FORALL_INT8X16_ASMJS_OP(SET_NATIVE_UINT8X16) SET_NATIVE_UINT8X16(fromInt8x16Bits) SET_NATIVE_UINT8X16(fromUint16x8Bits) SET_NATIVE_UINT8X16(fromUint32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Uint16x8: switch (global.simdOperation()) { FORALL_INT16X8_ASMJS_OP(SET_NATIVE_UINT16X8) SET_NATIVE_UINT16X8(fromUint8x16Bits) SET_NATIVE_UINT16X8(fromInt16x8Bits) SET_NATIVE_UINT16X8(fromUint32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Uint32x4: switch (global.simdOperation()) { FORALL_INT32X4_ASMJS_OP(SET_NATIVE_UINT32X4) SET_NATIVE_UINT32X4(fromUint8x16Bits) SET_NATIVE_UINT32X4(fromUint16x8Bits) SET_NATIVE_UINT32X4(fromInt32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Float32x4: switch (global.simdOperation()) { FORALL_FLOAT32X4_ASMJS_OP(SET_NATIVE_FLOAT32X4) SET_NATIVE_FLOAT32X4(fromUint8x16Bits) SET_NATIVE_FLOAT32X4(fromUint16x8Bits) SET_NATIVE_FLOAT32X4(fromUint32x4Bits) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Bool8x16: switch (global.simdOperation()) { FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL8X16) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Bool16x8: switch (global.simdOperation()) { FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL16X8) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; case SimdType::Bool32x4: switch (global.simdOperation()) { FORALL_BOOL_SIMD_OP(SET_NATIVE_BOOL32X4) default: MOZ_CRASH("shouldn't have been validated in the first place"); } break; default: MOZ_CRASH("unhandled simd type"); #undef FALLTHROUGH #undef SET_NATIVE_INT8X16 #undef SET_NATIVE_INT16X8 #undef SET_NATIVE_INT32X4 #undef SET_NATIVE_UINT8X16 #undef SET_NATIVE_UINT16X8 #undef SET_NATIVE_UINT32X4 #undef SET_NATIVE_FLOAT32X4 #undef SET_NATIVE_BOOL8X16 #undef SET_NATIVE_BOOL16X8 #undef SET_NATIVE_BOOL32X4 #undef SET_NATIVE } if (!native || !IsNativeFunction(v, native)) return LinkFail(cx, "bad SIMD.type.* operation"); return true; } static bool ValidateAtomicsBuiltinFunction(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { RootedValue v(cx); if (!GetDataProperty(cx, globalVal, cx->names().Atomics, &v)) return false; if (!GetDataProperty(cx, v, global.field(), &v)) return false; Native native = nullptr; switch (global.atomicsBuiltinFunction()) { case AsmJSAtomicsBuiltin_compareExchange: native = atomics_compareExchange; break; case AsmJSAtomicsBuiltin_exchange: native = atomics_exchange; break; case AsmJSAtomicsBuiltin_load: native = atomics_load; break; case AsmJSAtomicsBuiltin_store: native = atomics_store; break; case AsmJSAtomicsBuiltin_add: native = atomics_add; break; case AsmJSAtomicsBuiltin_sub: native = atomics_sub; break; case AsmJSAtomicsBuiltin_and: native = atomics_and; break; case AsmJSAtomicsBuiltin_or: native = atomics_or; break; case AsmJSAtomicsBuiltin_xor: native = atomics_xor; break; case AsmJSAtomicsBuiltin_isLockFree: native = atomics_isLockFree; break; } if (!IsNativeFunction(v, native)) return LinkFail(cx, "bad Atomics.* builtin function"); return true; } static bool ValidateConstant(JSContext* cx, const AsmJSGlobal& global, HandleValue globalVal) { RootedValue v(cx, globalVal); if (global.constantKind() == AsmJSGlobal::MathConstant) { if (!GetDataProperty(cx, v, cx->names().Math, &v)) return false; } if (!GetDataProperty(cx, v, global.field(), &v)) return false; if (!v.isNumber()) return LinkFail(cx, "math / global constant value needs to be a number"); // NaN != NaN if (IsNaN(global.constantValue())) { if (!IsNaN(v.toNumber())) return LinkFail(cx, "global constant value needs to be NaN"); } else { if (v.toNumber() != global.constantValue()) return LinkFail(cx, "global constant value mismatch"); } return true; } static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata, HandleValue bufferVal, MutableHandle buffer) { if (metadata.memoryUsage == MemoryUsage::Shared) { if (!IsSharedArrayBuffer(bufferVal)) return LinkFail(cx, "shared views can only be constructed onto SharedArrayBuffer"); } else { if (!IsArrayBuffer(bufferVal)) return LinkFail(cx, "unshared views can only be constructed onto ArrayBuffer"); } buffer.set(&AsAnyArrayBuffer(bufferVal)); uint32_t memoryLength = buffer->byteLength(); if (!IsValidAsmJSHeapLength(memoryLength)) { UniqueChars msg( JS_smprintf("ArrayBuffer byteLength 0x%x is not a valid heap length. The next " "valid length is 0x%x", memoryLength, RoundUpToNextValidAsmJSHeapLength(memoryLength))); if (!msg) return false; return LinkFail(cx, msg.get()); } // 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. MOZ_ASSERT((metadata.minMemoryLength - 1) <= INT32_MAX); if (memoryLength < metadata.minMemoryLength) { UniqueChars msg( JS_smprintf("ArrayBuffer byteLength of 0x%x is less than 0x%x (the size implied " "by const heap accesses).", memoryLength, metadata.minMemoryLength)); if (!msg) return false; return LinkFail(cx, msg.get()); } if (buffer->is()) { // On 64-bit, bounds checks are statically removed so the huge guard // region is always necessary. On 32-bit, allocating a guard page // requires reallocating the incoming ArrayBuffer which could trigger // OOM. Thus, only ask for a guard page when SIMD is used since SIMD // allows unaligned memory access (see MaxMemoryAccessSize comment); #ifdef WASM_HUGE_MEMORY bool needGuard = true; #else bool needGuard = metadata.usesSimd; #endif Rooted arrayBuffer(cx, &buffer->as()); if (!ArrayBufferObject::prepareForAsmJS(cx, arrayBuffer, needGuard)) return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); } else { if (!buffer->as().isPreparedForAsmJS()) return LinkFail(cx, "SharedArrayBuffer must be created with wasm test mode enabled"); } MOZ_ASSERT(buffer->isPreparedForAsmJS()); return true; } static bool GetImports(JSContext* cx, const AsmJSMetadata& metadata, HandleValue globalVal, HandleValue importVal, MutableHandle funcImports, ValVector* valImports) { Rooted ffis(cx, FunctionVector(cx)); if (!ffis.resize(metadata.numFFIs)) return false; for (const AsmJSGlobal& global : metadata.asmJSGlobals) { switch (global.which()) { case AsmJSGlobal::Variable: { Val val; if (!ValidateGlobalVariable(cx, global, importVal, &val)) return false; if (!valImports->append(val)) return false; break; } case AsmJSGlobal::FFI: if (!ValidateFFI(cx, global, importVal, &ffis)) return false; break; case AsmJSGlobal::ArrayView: case AsmJSGlobal::ArrayViewCtor: if (!ValidateArrayView(cx, global, globalVal)) return false; break; case AsmJSGlobal::MathBuiltinFunction: if (!ValidateMathBuiltinFunction(cx, global, globalVal)) return false; break; case AsmJSGlobal::AtomicsBuiltinFunction: if (!ValidateAtomicsBuiltinFunction(cx, global, globalVal)) return false; break; case AsmJSGlobal::Constant: if (!ValidateConstant(cx, global, globalVal)) return false; break; case AsmJSGlobal::SimdCtor: if (!ValidateSimdType(cx, global, globalVal)) return false; break; case AsmJSGlobal::SimdOp: if (!ValidateSimdOperation(cx, global, globalVal)) return false; break; } } for (const AsmJSImport& import : metadata.asmJSImports) { if (!funcImports.append(ffis[import.ffiIndex()])) return false; } return true; } static bool TryInstantiate(JSContext* cx, CallArgs args, Module& module, const AsmJSMetadata& metadata, MutableHandleWasmInstanceObject instanceObj, MutableHandleObject exportObj) { HandleValue globalVal = args.get(0); HandleValue importVal = args.get(1); HandleValue bufferVal = args.get(2); RootedArrayBufferObjectMaybeShared buffer(cx); RootedWasmMemoryObject memory(cx); if (module.metadata().usesMemory()) { if (!CheckBuffer(cx, metadata, bufferVal, &buffer)) return false; memory = WasmMemoryObject::create(cx, buffer, nullptr); if (!memory) return false; } ValVector valImports; Rooted funcs(cx, FunctionVector(cx)); if (!GetImports(cx, metadata, globalVal, importVal, &funcs, &valImports)) return false; RootedWasmTableObject table(cx); if (!module.instantiate(cx, funcs, table, memory, valImports, nullptr, instanceObj)) return false; RootedValue exportObjVal(cx); if (!JS_GetProperty(cx, instanceObj, InstanceExportField, &exportObjVal)) return false; MOZ_RELEASE_ASSERT(exportObjVal.isObject()); exportObj.set(&exportObjVal.toObject()); return true; } static bool HandleInstantiationFailure(JSContext* cx, CallArgs args, const AsmJSMetadata& metadata) { RootedAtom name(cx, args.callee().as().explicitName()); if (cx->isExceptionPending()) return false; ScriptSource* source = metadata.scriptSource.get(); // Source discarding is allowed to affect JS semantics because it is never // enabled for normal JS content. bool haveSource = source->hasSourceData(); if (!haveSource && !JSScript::loadSource(cx, source, &haveSource)) return false; if (!haveSource) { JS_ReportErrorASCII(cx, "asm.js link failure with source discarding enabled"); return false; } uint32_t begin = metadata.toStringStart; uint32_t end = metadata.srcEndAfterCurly(); Rooted src(cx, source->substringDontDeflate(cx, begin, end)); if (!src) return false; RootedFunction fun(cx, NewScriptedFunction(cx, 0, JSFunction::INTERPRETED_NORMAL, name, /* proto = */ nullptr, gc::AllocKind::FUNCTION, TenuredObject)); if (!fun) return false; CompileOptions options(cx); options.setMutedErrors(source->mutedErrors()) .setFile(source->filename()) .setNoScriptRval(false); options.asmJSOption = AsmJSOption::Disabled; // The exported function inherits an implicit strict context if the module // also inherited it somehow. if (metadata.strict) options.strictOption = true; AutoStableStringChars stableChars(cx); if (!stableChars.initTwoByte(cx, src)) return false; const char16_t* chars = stableChars.twoByteRange().begin().get(); SourceBufferHolder::Ownership ownership = stableChars.maybeGiveOwnershipToCaller() ? SourceBufferHolder::GiveOwnership : SourceBufferHolder::NoOwnership; SourceBufferHolder srcBuf(chars, end - begin, ownership); if (!frontend::CompileStandaloneFunction(cx, &fun, options, srcBuf, Nothing())) return false; // Call the function we just recompiled. args.setCallee(ObjectValue(*fun)); return InternalCallOrConstruct(cx, args, args.isConstructing() ? CONSTRUCT : NO_CONSTRUCT); } static 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. static bool InstantiateAsmJS(JSContext* cx, unsigned argc, JS::Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); JSFunction* callee = &args.callee().as(); Module& module = AsmJSModuleFunctionToModule(callee); const AsmJSMetadata& metadata = module.metadata().asAsmJS(); RootedWasmInstanceObject 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; } static JSFunction* NewAsmJSModuleFunction(ExclusiveContext* cx, JSFunction* origFun, HandleObject moduleObj) { RootedAtom name(cx, origFun->explicitName()); JSFunction::Flags flags = origFun->isLambda() ? JSFunction::ASMJS_LAMBDA_CTOR : JSFunction::ASMJS_CTOR; JSFunction* moduleFun = NewNativeConstructor(cx, InstantiateAsmJS, origFun->nargs(), name, gc::AllocKind::FUNCTION_EXTENDED, TenuredObject, flags); if (!moduleFun) return nullptr; moduleFun->setExtendedSlot(FunctionExtended::ASMJS_MODULE_SLOT, ObjectValue(*moduleObj)); MOZ_ASSERT(IsAsmJSModule(moduleFun)); return moduleFun; } /*****************************************************************************/ // Caching and cloning size_t AsmJSGlobal::serializedSize() const { return sizeof(pod) + field_.serializedSize(); } uint8_t* AsmJSGlobal::serialize(uint8_t* cursor) const { cursor = WriteBytes(cursor, &pod, sizeof(pod)); cursor = field_.serialize(cursor); return cursor; } const uint8_t* AsmJSGlobal::deserialize(const uint8_t* cursor) { (cursor = ReadBytes(cursor, &pod, sizeof(pod))) && (cursor = field_.deserialize(cursor)); return cursor; } size_t AsmJSGlobal::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return field_.sizeOfExcludingThis(mallocSizeOf); } size_t AsmJSMetadata::serializedSize() const { return Metadata::serializedSize() + sizeof(pod()) + SerializedVectorSize(asmJSGlobals) + SerializedPodVectorSize(asmJSImports) + SerializedPodVectorSize(asmJSExports) + SerializedVectorSize(asmJSFuncNames) + globalArgumentName.serializedSize() + importArgumentName.serializedSize() + bufferArgumentName.serializedSize(); } uint8_t* AsmJSMetadata::serialize(uint8_t* cursor) const { cursor = Metadata::serialize(cursor); cursor = WriteBytes(cursor, &pod(), sizeof(pod())); cursor = SerializeVector(cursor, asmJSGlobals); cursor = SerializePodVector(cursor, asmJSImports); cursor = SerializePodVector(cursor, asmJSExports); cursor = SerializeVector(cursor, asmJSFuncNames); cursor = globalArgumentName.serialize(cursor); cursor = importArgumentName.serialize(cursor); cursor = bufferArgumentName.serialize(cursor); return cursor; } const uint8_t* AsmJSMetadata::deserialize(const uint8_t* cursor) { (cursor = Metadata::deserialize(cursor)) && (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) && (cursor = DeserializeVector(cursor, &asmJSGlobals)) && (cursor = DeserializePodVector(cursor, &asmJSImports)) && (cursor = DeserializePodVector(cursor, &asmJSExports)) && (cursor = DeserializeVector(cursor, &asmJSFuncNames)) && (cursor = globalArgumentName.deserialize(cursor)) && (cursor = importArgumentName.deserialize(cursor)) && (cursor = bufferArgumentName.deserialize(cursor)); cacheResult = CacheResult::Hit; return cursor; } size_t AsmJSMetadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { return Metadata::sizeOfExcludingThis(mallocSizeOf) + SizeOfVectorExcludingThis(asmJSGlobals, mallocSizeOf) + asmJSImports.sizeOfExcludingThis(mallocSizeOf) + asmJSExports.sizeOfExcludingThis(mallocSizeOf) + SizeOfVectorExcludingThis(asmJSFuncNames, mallocSizeOf) + globalArgumentName.sizeOfExcludingThis(mallocSizeOf) + importArgumentName.sizeOfExcludingThis(mallocSizeOf) + bufferArgumentName.sizeOfExcludingThis(mallocSizeOf); } namespace { class ModuleChars { protected: uint32_t isFunCtor_; Vector funCtorArgs_; public: static uint32_t beginOffset(AsmJSParser& parser) { return parser.pc->functionBox()->functionNode->pn_pos.begin; } static uint32_t endOffset(AsmJSParser& parser) { TokenPos pos(0, 0); // initialize to silence GCC warning MOZ_ALWAYS_TRUE(parser.tokenStream.peekTokenPos(&pos, TokenStream::Operand)); return pos.end; } }; class ModuleCharsForStore : ModuleChars { uint32_t uncompressedSize_; uint32_t compressedSize_; Vector compressedBuffer_; public: bool init(AsmJSParser& parser) { MOZ_ASSERT(beginOffset(parser) < endOffset(parser)); uncompressedSize_ = (endOffset(parser) - beginOffset(parser)) * sizeof(char16_t); size_t maxCompressedSize = LZ4::maxCompressedSize(uncompressedSize_); if (maxCompressedSize < uncompressedSize_) return false; if (!compressedBuffer_.resize(maxCompressedSize)) return false; const char16_t* chars = parser.tokenStream.rawCharPtrAt(beginOffset(parser)); const char* source = reinterpret_cast(chars); size_t compressedSize = LZ4::compress(source, uncompressedSize_, compressedBuffer_.begin()); if (!compressedSize || compressedSize > UINT32_MAX) return false; compressedSize_ = compressedSize; // For a function statement or named function expression: // function f(x,y,z) { abc } // the range [beginOffset, endOffset) captures the source: // f(x,y,z) { abc } // An unnamed function expression captures the same thing, sans 'f'. // Since asm.js modules do not contain any free variables, equality of // [beginOffset, endOffset) is sufficient to guarantee identical code // generation, modulo Assumptions. // // For functions created with 'new Function', function arguments are // not present in the source so we must manually explicitly serialize // and match the formals as a Vector of PropertyName. isFunCtor_ = parser.pc->isStandaloneFunctionBody(); if (isFunCtor_) { unsigned numArgs; ParseNode* functionNode = parser.pc->functionBox()->functionNode; ParseNode* arg = FunctionFormalParametersList(functionNode, &numArgs); for (unsigned i = 0; i < numArgs; i++, arg = arg->pn_next) { UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->name()); if (!name || !funCtorArgs_.append(Move(name))) return false; } } return true; } size_t serializedSize() const { return sizeof(uint32_t) + sizeof(uint32_t) + compressedSize_ + sizeof(uint32_t) + (isFunCtor_ ? SerializedVectorSize(funCtorArgs_) : 0); } uint8_t* serialize(uint8_t* cursor) const { cursor = WriteScalar(cursor, uncompressedSize_); cursor = WriteScalar(cursor, compressedSize_); cursor = WriteBytes(cursor, compressedBuffer_.begin(), compressedSize_); cursor = WriteScalar(cursor, isFunCtor_); if (isFunCtor_) cursor = SerializeVector(cursor, funCtorArgs_); return cursor; } }; class ModuleCharsForLookup : ModuleChars { Vector chars_; public: const uint8_t* deserialize(const uint8_t* cursor) { uint32_t uncompressedSize; cursor = ReadScalar(cursor, &uncompressedSize); uint32_t compressedSize; cursor = ReadScalar(cursor, &compressedSize); if (!chars_.resize(uncompressedSize / sizeof(char16_t))) return nullptr; const char* source = reinterpret_cast(cursor); char* dest = reinterpret_cast(chars_.begin()); if (!LZ4::decompress(source, dest, uncompressedSize)) return nullptr; cursor += compressedSize; cursor = ReadScalar(cursor, &isFunCtor_); if (isFunCtor_) cursor = DeserializeVector(cursor, &funCtorArgs_); return cursor; } bool match(AsmJSParser& parser) const { const char16_t* parseBegin = parser.tokenStream.rawCharPtrAt(beginOffset(parser)); const char16_t* parseLimit = parser.tokenStream.rawLimit(); MOZ_ASSERT(parseLimit >= parseBegin); if (uint32_t(parseLimit - parseBegin) < chars_.length()) return false; if (!PodEqual(chars_.begin(), parseBegin, chars_.length())) return false; if (isFunCtor_ != parser.pc->isStandaloneFunctionBody()) return false; if (isFunCtor_) { // For function statements, the closing } is included as the last // character of the matched source. For Function constructor, // parsing terminates with EOF which we must explicitly check. This // prevents // new Function('"use asm"; function f() {} return f') // from incorrectly matching // new Function('"use asm"; function f() {} return ff') if (parseBegin + chars_.length() != parseLimit) return false; unsigned numArgs; ParseNode* functionNode = parser.pc->functionBox()->functionNode; ParseNode* arg = FunctionFormalParametersList(functionNode, &numArgs); if (funCtorArgs_.length() != numArgs) return false; for (unsigned i = 0; i < funCtorArgs_.length(); i++, arg = arg->pn_next) { UniqueChars name = StringToNewUTF8CharsZ(nullptr, *arg->name()); if (!name || strcmp(funCtorArgs_[i].get(), name.get())) return false; } } return true; } }; struct ScopedCacheEntryOpenedForWrite { ExclusiveContext* cx; const size_t serializedSize; uint8_t* memory; intptr_t handle; ScopedCacheEntryOpenedForWrite(ExclusiveContext* cx, size_t serializedSize) : cx(cx), serializedSize(serializedSize), memory(nullptr), handle(-1) {} ~ScopedCacheEntryOpenedForWrite() { if (memory) cx->asmJSCacheOps().closeEntryForWrite(serializedSize, memory, handle); } }; struct ScopedCacheEntryOpenedForRead { ExclusiveContext* cx; size_t serializedSize; const uint8_t* memory; intptr_t handle; explicit ScopedCacheEntryOpenedForRead(ExclusiveContext* cx) : cx(cx), serializedSize(0), memory(nullptr), handle(0) {} ~ScopedCacheEntryOpenedForRead() { if (memory) cx->asmJSCacheOps().closeEntryForRead(serializedSize, memory, handle); } }; } // unnamed namespace static JS::AsmJSCacheResult StoreAsmJSModuleInCache(AsmJSParser& parser, Module& module, ExclusiveContext* cx) { ModuleCharsForStore moduleChars; if (!moduleChars.init(parser)) return JS::AsmJSCache_InternalError; size_t bytecodeSize, compiledSize; module.serializedSize(&bytecodeSize, &compiledSize); MOZ_RELEASE_ASSERT(bytecodeSize == 0); MOZ_RELEASE_ASSERT(compiledSize <= UINT32_MAX); size_t serializedSize = sizeof(uint32_t) + compiledSize + moduleChars.serializedSize(); JS::OpenAsmJSCacheEntryForWriteOp open = cx->asmJSCacheOps().openEntryForWrite; if (!open) return JS::AsmJSCache_Disabled_Internal; const char16_t* begin = parser.tokenStream.rawCharPtrAt(ModuleChars::beginOffset(parser)); const char16_t* end = parser.tokenStream.rawCharPtrAt(ModuleChars::endOffset(parser)); bool installed = parser.options().installedFile; ScopedCacheEntryOpenedForWrite entry(cx, serializedSize); JS::AsmJSCacheResult openResult = open(cx->global(), installed, begin, end, serializedSize, &entry.memory, &entry.handle); if (openResult != JS::AsmJSCache_Success) return openResult; uint8_t* cursor = entry.memory; // Everything serialized before the Module must not change incompatibly // between any two builds (regardless of platform, architecture, ...). // (The Module::assumptionsMatch() guard everything in the Module and // afterwards.) cursor = WriteScalar(cursor, compiledSize); module.serialize(/* bytecodeBegin = */ nullptr, /* bytecodeSize = */ 0, cursor, compiledSize); cursor += compiledSize; cursor = moduleChars.serialize(cursor); MOZ_RELEASE_ASSERT(cursor == entry.memory + serializedSize); return JS::AsmJSCache_Success; } static bool LookupAsmJSModuleInCache(ExclusiveContext* cx, AsmJSParser& parser, bool* loadedFromCache, SharedModule* module, UniqueChars* compilationTimeReport) { int64_t before = PRMJ_Now(); *loadedFromCache = false; JS::OpenAsmJSCacheEntryForReadOp open = cx->asmJSCacheOps().openEntryForRead; if (!open) return true; const char16_t* begin = parser.tokenStream.rawCharPtrAt(ModuleChars::beginOffset(parser)); const char16_t* limit = parser.tokenStream.rawLimit(); ScopedCacheEntryOpenedForRead entry(cx); if (!open(cx->global(), begin, limit, &entry.serializedSize, &entry.memory, &entry.handle)) return true; size_t remain = entry.serializedSize; const uint8_t* cursor = entry.memory; uint32_t compiledSize; cursor = ReadScalarChecked(cursor, &remain, &compiledSize); if (!cursor) return true; Assumptions assumptions; if (!assumptions.initBuildIdFromContext(cx)) return false; if (!Module::assumptionsMatch(assumptions, cursor, remain)) return true; MutableAsmJSMetadata asmJSMetadata = cx->new_(); if (!asmJSMetadata) return false; *module = Module::deserialize(/* bytecodeBegin = */ nullptr, /* bytecodeSize = */ 0, cursor, compiledSize, asmJSMetadata.get()); if (!*module) { ReportOutOfMemory(cx); return false; } cursor += compiledSize; // Due to the hash comparison made by openEntryForRead, this should succeed // with high probability. ModuleCharsForLookup moduleChars; cursor = moduleChars.deserialize(cursor); if (!moduleChars.match(parser)) return true; // Don't punish release users by crashing if there is a programmer error // here, just gracefully return with a cache miss. #ifdef NIGHTLY_BUILD MOZ_RELEASE_ASSERT(cursor == entry.memory + entry.serializedSize); #endif if (cursor != entry.memory + entry.serializedSize) return true; // See AsmJSMetadata comment as well as ModuleValidator::init(). asmJSMetadata->toStringStart = parser.pc->functionBox()->toStringStart; asmJSMetadata->srcStart = parser.pc->functionBox()->functionNode->pn_body->pn_pos.begin; asmJSMetadata->srcBodyStart = parser.tokenStream.currentToken().pos.end; asmJSMetadata->strict = parser.pc->sc()->strict() && !parser.pc->sc()->hasExplicitUseStrict(); asmJSMetadata->scriptSource.reset(parser.ss); if (!parser.tokenStream.advance(asmJSMetadata->srcEndBeforeCurly())) return false; int64_t after = PRMJ_Now(); int ms = (after - before) / PRMJ_USEC_PER_MSEC; *compilationTimeReport = UniqueChars(JS_smprintf("loaded from cache in %dms", ms)); if (!*compilationTimeReport) return false; *loadedFromCache = true; return true; } /*****************************************************************************/ // Top-level js::CompileAsmJS static bool NoExceptionPending(ExclusiveContext* cx) { return !cx->isJSContext() || !cx->asJSContext()->isExceptionPending(); } static bool Warn(AsmJSParser& parser, int errorNumber, const char* str) { ParseReportKind reportKind = parser.options().throwOnAsmJSValidationFailureOption && errorNumber == JSMSG_USE_ASM_TYPE_FAIL ? ParseError : ParseWarning; parser.reportNoOffset(reportKind, /* strict = */ false, errorNumber, str ? str : ""); return false; } static bool EstablishPreconditions(ExclusiveContext* cx, AsmJSParser& parser) { if (!HasCompilerSupport(cx)) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by lack of compiler support"); switch (parser.options().asmJSOption) { case AsmJSOption::Disabled: return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by 'asmjs' runtime option"); case AsmJSOption::DisabledByDebugger: return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by debugger"); case AsmJSOption::Enabled: break; } if (parser.pc->isGenerator()) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by generator context"); if (parser.pc->isArrowFunction()) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by arrow function context"); // Class constructors are also methods if (parser.pc->isMethod()) return Warn(parser, JSMSG_USE_ASM_TYPE_FAIL, "Disabled by class constructor or method context"); return true; } static UniqueChars BuildConsoleMessage(ExclusiveContext* cx, unsigned time, JS::AsmJSCacheResult cacheResult) { #ifndef JS_MORE_DETERMINISTIC const char* cacheString = ""; switch (cacheResult) { case JS::AsmJSCache_Success: cacheString = "stored in cache"; break; case JS::AsmJSCache_ModuleTooSmall: cacheString = "not stored in cache (too small to benefit)"; break; case JS::AsmJSCache_SynchronousScript: cacheString = "unable to cache asm.js in synchronous scripts; try loading " "asm.js via