Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
*
* Copyright 2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/AsmJS.h"
#include "mozilla/Attributes.h"
#include "mozilla/Compression.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Maybe.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h" // SprintfLiteral
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "mozilla/Variant.h"
#include <algorithm>
#include <new>
#include "jsmath.h"
#include "frontend/FunctionSyntaxKind.h" // FunctionSyntaxKind
#include "frontend/ParseNode.h"
#include "frontend/Parser.h"
#include "frontend/ParserAtom.h" // ParserAtomsTable, TaggedParserAtomIndex
#include "frontend/SharedContext.h" // TopLevelFunction
#include "frontend/TaggedParserAtomIndexHasher.h" // TaggedParserAtomIndexHasher
#include "gc/Policy.h"
#include "js/BuildId.h" // JS::BuildIdCharVector
#include "js/friend/ErrorMessages.h" // JSMSG_*
#include "js/MemoryMetrics.h"
#include "js/Printf.h"
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "util/DifferentialTesting.h"
#include "util/StringBuffer.h"
#include "util/Text.h"
#include "vm/ErrorReporting.h"
#include "vm/FunctionFlags.h" // js::FunctionFlags
#include "vm/GeneratorAndAsyncKind.h" // js::GeneratorKind, js::FunctionAsyncKind
#include "vm/SelfHosting.h"
#include "vm/Time.h"
#include "vm/TypedArrayObject.h"
#include "vm/Warnings.h" // js::WarnNumberASCII
#include "wasm/WasmCompile.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmSerialize.h"
#include "wasm/WasmValidate.h"
#include "frontend/SharedContext-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/JSObject-inl.h"
#include "wasm/WasmInstance-inl.h"
using namespace js;
using namespace js::frontend;
using namespace js::jit;
using namespace js::wasm;
using JS::AsmJSOption;
using JS::AutoStableStringChars;
using JS::GenericNaN;
using JS::SourceOwnership;
using JS::SourceText;
using mozilla::Abs;
using mozilla::AsVariant;
using mozilla::CeilingLog2;
using mozilla::HashGeneric;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using mozilla::IsPositiveZero;
using mozilla::IsPowerOfTwo;
using mozilla::Nothing;
using mozilla::PodZero;
using mozilla::PositiveInfinity;
using mozilla::Some;
using mozilla::Utf8Unit;
using mozilla::Compression::LZ4;
/*****************************************************************************/
// A wasm module can either use no memory, a unshared memory (ArrayBuffer) or
// shared memory (SharedArrayBuffer).
enum class MemoryUsage { None = false, Unshared = 1, Shared = 2 };
// The asm.js valid heap lengths are precisely the WASM valid heap lengths for
// ARM greater or equal to MinHeapLength
static const size_t MinHeapLength = PageSize;
// An asm.js heap can in principle be up to INT32_MAX bytes but requirements
// on the format restrict it further to the largest pseudo-ARM-immediate.
// See IsValidAsmJSHeapLength().
static const uint64_t MaxHeapLength = 0x7f000000;
static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) {
if (length <= MinHeapLength) {
return MinHeapLength;
}
return wasm::RoundUpToNextValidARMImmediate(length);
}
static uint64_t DivideRoundingUp(uint64_t a, uint64_t b) {
return (a + (b - 1)) / b;
}
/*****************************************************************************/
// asm.js module object
// The asm.js spec recognizes this set of builtin Math functions.
enum AsmJSMathBuiltinFunction {
AsmJSMathBuiltin_sin,
AsmJSMathBuiltin_cos,
AsmJSMathBuiltin_tan,
AsmJSMathBuiltin_asin,
AsmJSMathBuiltin_acos,
AsmJSMathBuiltin_atan,
AsmJSMathBuiltin_ceil,
AsmJSMathBuiltin_floor,
AsmJSMathBuiltin_exp,
AsmJSMathBuiltin_log,
AsmJSMathBuiltin_pow,
AsmJSMathBuiltin_sqrt,
AsmJSMathBuiltin_abs,
AsmJSMathBuiltin_atan2,
AsmJSMathBuiltin_imul,
AsmJSMathBuiltin_fround,
AsmJSMathBuiltin_min,
AsmJSMathBuiltin_max,
AsmJSMathBuiltin_clz32
};
// LitValPOD is a restricted version of LitVal suitable for asm.js that is
// always POD.
struct LitValPOD {
PackedTypeCode valType_;
union U {
uint32_t u32_;
uint64_t u64_;
float f32_;
double f64_;
} u;
LitValPOD() = default;
explicit LitValPOD(uint32_t u32) : valType_(ValType(ValType::I32).packed()) {
u.u32_ = u32;
}
explicit LitValPOD(uint64_t u64) : valType_(ValType(ValType::I64).packed()) {
u.u64_ = u64;
}
explicit LitValPOD(float f32) : valType_(ValType(ValType::F32).packed()) {
u.f32_ = f32;
}
explicit LitValPOD(double f64) : valType_(ValType(ValType::F64).packed()) {
u.f64_ = f64;
}
LitVal asLitVal() const {
switch (valType_.typeCode()) {
case TypeCode::I32:
return LitVal(u.u32_);
case TypeCode::I64:
return LitVal(u.u64_);
case TypeCode::F32:
return LitVal(u.f32_);
case TypeCode::F64:
return LitVal(u.f64_);
default:
MOZ_CRASH("Can't happen");
}
}
};
static_assert(std::is_pod_v<LitValPOD>,
"must be POD to be simply serialized/deserialized");
// An AsmJSGlobal represents a JS global variable in the asm.js module function.
class AsmJSGlobal {
public:
enum Which {
Variable,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction,
Constant
};
enum VarInitKind { InitConstant, InitImport };
enum ConstantKind { GlobalConstant, MathConstant };
private:
struct CacheablePod {
Which which_;
union V {
struct {
VarInitKind initKind_;
union U {
PackedTypeCode importValType_;
LitValPOD val_;
} u;
} var;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
struct {
ConstantKind kind_;
double value_;
} constant;
} u;
} pod;
CacheableChars field_;
friend class ModuleValidatorShared;
template <typename Unit>
friend class ModuleValidator;
public:
AsmJSGlobal() = default;
AsmJSGlobal(Which which, UniqueChars field) {
mozilla::PodZero(&pod); // zero padding for Valgrind
pod.which_ = which;
field_ = std::move(field);
}
const char* field() const { return field_.get(); }
Which which() const { return pod.which_; }
VarInitKind varInitKind() const {
MOZ_ASSERT(pod.which_ == Variable);
return pod.u.var.initKind_;
}
LitValPOD varInitVal() const {
MOZ_ASSERT(pod.which_ == Variable);
MOZ_ASSERT(pod.u.var.initKind_ == InitConstant);
return pod.u.var.u.val_;
}
ValType varInitImportType() const {
MOZ_ASSERT(pod.which_ == Variable);
MOZ_ASSERT(pod.u.var.initKind_ == InitImport);
return ValType(pod.u.var.u.importValType_);
}
uint32_t ffiIndex() const {
MOZ_ASSERT(pod.which_ == FFI);
return pod.u.ffiIndex_;
}
// When a view is created from an imported constructor:
// var I32 = stdlib.Int32Array;
// var i32 = new I32(buffer);
// the second import has nothing to validate and thus has a null field.
Scalar::Type viewType() const {
MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == ArrayViewCtor);
return pod.u.viewType_;
}
AsmJSMathBuiltinFunction mathBuiltinFunction() const {
MOZ_ASSERT(pod.which_ == MathBuiltinFunction);
return pod.u.mathBuiltinFunc_;
}
ConstantKind constantKind() const {
MOZ_ASSERT(pod.which_ == Constant);
return pod.u.constant.kind_;
}
double constantValue() const {
MOZ_ASSERT(pod.which_ == Constant);
return pod.u.constant.value_;
}
};
using AsmJSGlobalVector = Vector<AsmJSGlobal, 0, SystemAllocPolicy>;
// An AsmJSImport is slightly different than an asm.js FFI function: a single
// asm.js FFI function can be called with many different signatures. When
// compiled to wasm, each unique FFI function paired with signature generates a
// wasm import.
class AsmJSImport {
uint32_t ffiIndex_;
public:
AsmJSImport() = default;
explicit AsmJSImport(uint32_t ffiIndex) : ffiIndex_(ffiIndex) {}
uint32_t ffiIndex() const { return ffiIndex_; }
};
using AsmJSImportVector = Vector<AsmJSImport, 0, SystemAllocPolicy>;
// An AsmJSExport logically extends Export with the extra information needed for
// an asm.js exported function, viz., the offsets in module's source chars in
// case the function is toString()ed.
class AsmJSExport {
uint32_t funcIndex_ = 0;
// All fields are treated as cacheable POD:
uint32_t startOffsetInModule_ = 0; // Store module-start-relative offsets
uint32_t endOffsetInModule_ = 0; // so preserved by serialization.
public:
AsmJSExport() = default;
AsmJSExport(uint32_t funcIndex, uint32_t startOffsetInModule,
uint32_t endOffsetInModule)
: funcIndex_(funcIndex),
startOffsetInModule_(startOffsetInModule),
endOffsetInModule_(endOffsetInModule) {}
uint32_t funcIndex() const { return funcIndex_; }
uint32_t startOffsetInModule() const { return startOffsetInModule_; }
uint32_t endOffsetInModule() const { return endOffsetInModule_; }
};
using AsmJSExportVector = Vector<AsmJSExport, 0, SystemAllocPolicy>;
// Holds the immutable guts of an AsmJSModule.
//
// AsmJSMetadata is built incrementally by ModuleValidator and then shared
// immutably between AsmJSModules.
struct AsmJSMetadataCacheablePod {
uint32_t numFFIs = 0;
uint32_t srcLength = 0;
uint32_t srcLengthWithRightBrace = 0;
AsmJSMetadataCacheablePod() = default;
};
struct js::AsmJSMetadata : Metadata, AsmJSMetadataCacheablePod {
AsmJSGlobalVector asmJSGlobals;
AsmJSImportVector asmJSImports;
AsmJSExportVector asmJSExports;
CacheableCharsVector asmJSFuncNames;
CacheableChars globalArgumentName;
CacheableChars importArgumentName;
CacheableChars bufferArgumentName;
// These values are not serialized since they are relative to the
// containing script which can be different between serialization and
// deserialization contexts. Thus, they must be set explicitly using the
// ambient Parser/ScriptSource after deserialization.
//
// srcStart refers to the offset in the ScriptSource to the beginning of
// the asm.js module function. If the function has been created with the
// Function constructor, this will be the first character in the function
// source. Otherwise, it will be the opening parenthesis of the arguments
// list.
uint32_t toStringStart;
uint32_t srcStart;
bool strict;
RefPtr<ScriptSource> source;
uint32_t srcEndBeforeCurly() const { return srcStart + srcLength; }
uint32_t srcEndAfterCurly() const {
return srcStart + srcLengthWithRightBrace;
}
AsmJSMetadata()
: Metadata(ModuleKind::AsmJS),
toStringStart(0),
srcStart(0),
strict(false) {}
~AsmJSMetadata() override = default;
const AsmJSExport& lookupAsmJSExport(uint32_t funcIndex) const {
// The AsmJSExportVector isn't stored in sorted order so do a linear
// search. This is for the super-cold and already-expensive toString()
// path and the number of exports is generally small.
for (const AsmJSExport& exp : asmJSExports) {
if (exp.funcIndex() == funcIndex) {
return exp;
}
}
MOZ_CRASH("missing asm.js func export");
}
bool mutedErrors() const override { return source->mutedErrors(); }
const char16_t* displayURL() const override {
return source->hasDisplayURL() ? source->displayURL() : nullptr;
}
ScriptSource* maybeScriptSource() const override { return source.get(); }
bool getFuncName(NameContext ctx, uint32_t funcIndex,
UTF8Bytes* name) const override {
const char* p = asmJSFuncNames[funcIndex].get();
if (!p) {
return true;
}
return name->append(p, strlen(p));
}
AsmJSMetadataCacheablePod& pod() { return *this; }
const AsmJSMetadataCacheablePod& pod() const { return *this; }
};
using MutableAsmJSMetadata = RefPtr<AsmJSMetadata>;
/*****************************************************************************/
// ParseNode utilities
static inline ParseNode* NextNode(ParseNode* pn) { return pn->pn_next; }
static inline ParseNode* UnaryKid(ParseNode* pn) {
return pn->as<UnaryNode>().kid();
}
static inline ParseNode* BinaryRight(ParseNode* pn) {
return pn->as<BinaryNode>().right();
}
static inline ParseNode* BinaryLeft(ParseNode* pn) {
return pn->as<BinaryNode>().left();
}
static inline ParseNode* ReturnExpr(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ReturnStmt));
return UnaryKid(pn);
}
static inline ParseNode* TernaryKid1(ParseNode* pn) {
return pn->as<TernaryNode>().kid1();
}
static inline ParseNode* TernaryKid2(ParseNode* pn) {
return pn->as<TernaryNode>().kid2();
}
static inline ParseNode* TernaryKid3(ParseNode* pn) {
return pn->as<TernaryNode>().kid3();
}
static inline ParseNode* ListHead(ParseNode* pn) {
return pn->as<ListNode>().head();
}
static inline unsigned ListLength(ParseNode* pn) {
return pn->as<ListNode>().count();
}
static inline ParseNode* CallCallee(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return BinaryLeft(pn);
}
static inline unsigned CallArgListLength(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return ListLength(BinaryRight(pn));
}
static inline ParseNode* CallArgList(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::CallExpr));
return ListHead(BinaryRight(pn));
}
static inline ParseNode* VarListHead(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::VarStmt) ||
pn->isKind(ParseNodeKind::ConstDecl));
return ListHead(pn);
}
static inline bool IsDefaultCase(ParseNode* pn) {
return pn->as<CaseClause>().isDefault();
}
static inline ParseNode* CaseExpr(ParseNode* pn) {
return pn->as<CaseClause>().caseExpression();
}
static inline ParseNode* CaseBody(ParseNode* pn) {
return pn->as<CaseClause>().statementList();
}
static inline ParseNode* BinaryOpLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isBinaryOperation());
MOZ_ASSERT(pn->as<ListNode>().count() == 2);
return ListHead(pn);
}
static inline ParseNode* BinaryOpRight(ParseNode* pn) {
MOZ_ASSERT(pn->isBinaryOperation());
MOZ_ASSERT(pn->as<ListNode>().count() == 2);
return NextNode(ListHead(pn));
}
static inline ParseNode* BitwiseLeft(ParseNode* pn) { return BinaryOpLeft(pn); }
static inline ParseNode* BitwiseRight(ParseNode* pn) {
return BinaryOpRight(pn);
}
static inline ParseNode* MultiplyLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* MultiplyRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::MulExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* AddSubLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
pn->isKind(ParseNodeKind::SubExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* AddSubRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::AddExpr) ||
pn->isKind(ParseNodeKind::SubExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* DivOrModLeft(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
pn->isKind(ParseNodeKind::ModExpr));
return BinaryOpLeft(pn);
}
static inline ParseNode* DivOrModRight(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::DivExpr) ||
pn->isKind(ParseNodeKind::ModExpr));
return BinaryOpRight(pn);
}
static inline ParseNode* ComparisonLeft(ParseNode* pn) {
return BinaryOpLeft(pn);
}
static inline ParseNode* ComparisonRight(ParseNode* pn) {
return BinaryOpRight(pn);
}
static inline bool IsExpressionStatement(ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt);
}
static inline ParseNode* ExpressionStatementExpr(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::ExpressionStmt));
return UnaryKid(pn);
}
static inline TaggedParserAtomIndex LoopControlMaybeLabel(ParseNode* pn) {
MOZ_ASSERT(pn->isKind(ParseNodeKind::BreakStmt) ||
pn->isKind(ParseNodeKind::ContinueStmt));
return pn->as<LoopControlStatement>().label();
}
static inline TaggedParserAtomIndex LabeledStatementLabel(ParseNode* pn) {
return pn->as<LabeledStatement>().label();
}
static inline ParseNode* LabeledStatementStatement(ParseNode* pn) {
return pn->as<LabeledStatement>().statement();
}
static double NumberNodeValue(ParseNode* pn) {
return pn->as<NumericLiteral>().value();
}
static bool NumberNodeHasFrac(ParseNode* pn) {
return pn->as<NumericLiteral>().decimalPoint() == HasDecimal;
}
static ParseNode* DotBase(ParseNode* pn) {
return &pn->as<PropertyAccess>().expression();
}
static TaggedParserAtomIndex DotMember(ParseNode* pn) {
return pn->as<PropertyAccess>().name();
}
static ParseNode* ElemBase(ParseNode* pn) {
return &pn->as<PropertyByValue>().expression();
}
static ParseNode* ElemIndex(ParseNode* pn) {
return &pn->as<PropertyByValue>().key();
}
static inline TaggedParserAtomIndex FunctionName(FunctionNode* funNode) {
if (auto name = funNode->funbox()->explicitName()) {
return name;
}
return TaggedParserAtomIndex::null();
}
static inline ParseNode* FunctionStatementList(FunctionNode* funNode) {
MOZ_ASSERT(funNode->body()->isKind(ParseNodeKind::ParamsBody));
LexicalScopeNode* last =
&funNode->body()->as<ListNode>().last()->as<LexicalScopeNode>();
MOZ_ASSERT(last->isEmptyScope());
ParseNode* body = last->scopeBody();
MOZ_ASSERT(body->isKind(ParseNodeKind::StatementList));
return body;
}
static inline bool IsNormalObjectField(ParseNode* pn) {
return pn->isKind(ParseNodeKind::PropertyDefinition) &&
pn->as<PropertyDefinition>().accessorType() == AccessorType::None &&
BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName);
}
static inline TaggedParserAtomIndex ObjectNormalFieldName(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
MOZ_ASSERT(BinaryLeft(pn)->isKind(ParseNodeKind::ObjectPropertyName));
return BinaryLeft(pn)->as<NameNode>().atom();
}
static inline ParseNode* ObjectNormalFieldInitializer(ParseNode* pn) {
MOZ_ASSERT(IsNormalObjectField(pn));
return BinaryRight(pn);
}
static inline bool IsUseOfName(ParseNode* pn, TaggedParserAtomIndex name) {
return pn->isName(name);
}
static inline bool IsIgnoredDirectiveName(JSContext* cx,
TaggedParserAtomIndex atom) {
return atom != TaggedParserAtomIndex::WellKnown::useStrict();
}
static inline bool IsIgnoredDirective(JSContext* cx, ParseNode* pn) {
return pn->isKind(ParseNodeKind::ExpressionStmt) &&
UnaryKid(pn)->isKind(ParseNodeKind::StringExpr) &&
IsIgnoredDirectiveName(cx, UnaryKid(pn)->as<NameNode>().atom());
}
static inline bool IsEmptyStatement(ParseNode* pn) {
return pn->isKind(ParseNodeKind::EmptyStmt);
}
static inline ParseNode* SkipEmptyStatements(ParseNode* pn) {
while (pn && IsEmptyStatement(pn)) {
pn = pn->pn_next;
}
return pn;
}
static inline ParseNode* NextNonEmptyStatement(ParseNode* pn) {
return SkipEmptyStatements(pn->pn_next);
}
template <typename Unit>
static bool GetToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
auto& ts = parser.tokenStream;
TokenKind tk;
while (true) {
if (!ts.getToken(&tk, TokenStreamShared::SlashIsRegExp)) {
return false;
}
if (tk != TokenKind::Semi) {
break;
}
}
*tkp = tk;
return true;
}
template <typename Unit>
static bool PeekToken(AsmJSParser<Unit>& parser, TokenKind* tkp) {
auto& ts = parser.tokenStream;
TokenKind tk;
while (true) {
if (!ts.peekToken(&tk, TokenStream::SlashIsRegExp)) {
return false;
}
if (tk != TokenKind::Semi) {
break;
}
ts.consumeKnownToken(TokenKind::Semi, TokenStreamShared::SlashIsRegExp);
}
*tkp = tk;
return true;
}
template <typename Unit>
static bool ParseVarOrConstStatement(AsmJSParser<Unit>& parser,
ParseNode** var) {
TokenKind tk;
if (!PeekToken(parser, &tk)) {
return false;
}
if (tk != TokenKind::Var && tk != TokenKind::Const) {
*var = nullptr;
return true;
}
*var = parser.statementListItem(YieldIsName);
if (!*var) {
return false;
}
MOZ_ASSERT((*var)->isKind(ParseNodeKind::VarStmt) ||
(*var)->isKind(ParseNodeKind::ConstDecl));
return true;
}
/*****************************************************************************/
// Represents the type and value of an asm.js numeric literal.
//
// A literal is a double iff the literal contains a decimal point (even if the
// fractional part is 0). Otherwise, integers may be classified:
// fixnum: [0, 2^31)
// negative int: [-2^31, 0)
// big unsigned: [2^31, 2^32)
// out of range: otherwise
// Lastly, a literal may be a float literal which is any double or integer
// literal coerced with Math.fround.
class NumLit {
public:
enum Which {
Fixnum,
NegativeInt,
BigUnsigned,
Double,
Float,
OutOfRangeInt = -1
};
private:
Which which_;
JS::Value value_;
public:
NumLit() = default;
NumLit(Which w, const Value& v) : which_(w), value_(v) {}
Which which() const { return which_; }
int32_t toInt32() const {
MOZ_ASSERT(which_ == Fixnum || which_ == NegativeInt ||
which_ == BigUnsigned);
return value_.toInt32();
}
uint32_t toUint32() const { return (uint32_t)toInt32(); }
double toDouble() const {
MOZ_ASSERT(which_ == Double);
return value_.toDouble();
}
float toFloat() const {
MOZ_ASSERT(which_ == Float);
return float(value_.toDouble());
}
Value scalarValue() const {
MOZ_ASSERT(which_ != OutOfRangeInt);
return value_;
}
bool valid() const { return which_ != OutOfRangeInt; }
bool isZeroBits() const {
MOZ_ASSERT(valid());
switch (which()) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return toInt32() == 0;
case NumLit::Double:
return IsPositiveZero(toDouble());
case NumLit::Float:
return IsPositiveZero(toFloat());
case NumLit::OutOfRangeInt:
MOZ_CRASH("can't be here because of valid() check above");
}
return false;
}
LitValPOD value() const {
switch (which_) {
case NumLit::Fixnum:
case NumLit::NegativeInt:
case NumLit::BigUnsigned:
return LitValPOD(toUint32());
case NumLit::Float:
return LitValPOD(toFloat());
case NumLit::Double:
return LitValPOD(toDouble());
case NumLit::OutOfRangeInt:;
}
MOZ_CRASH("bad literal");
}
};
// Represents the type of a general asm.js expression.
//
// A canonical subset of types representing the coercion targets: Int, Float,
// Double.
//
// Void is also part of the canonical subset.
class Type {
public:
enum Which {
Fixnum = NumLit::Fixnum,
Signed = NumLit::NegativeInt,
Unsigned = NumLit::BigUnsigned,
DoubleLit = NumLit::Double,
Float = NumLit::Float,
Double,
MaybeDouble,
MaybeFloat,
Floatish,
Int,
Intish,
Void
};
private:
Which which_;
public:
Type() = default;
MOZ_IMPLICIT Type(Which w) : which_(w) {}
// Map an already canonicalized Type to the return type of a function call.
static Type ret(Type t) {
MOZ_ASSERT(t.isCanonical());
// The 32-bit external type is Signed, not Int.
return t.isInt() ? Signed : t;
}
static Type lit(const NumLit& lit) {
MOZ_ASSERT(lit.valid());
Which which = Type::Which(lit.which());
MOZ_ASSERT(which >= Fixnum && which <= Float);
Type t;
t.which_ = which;
return t;
}
// Map |t| to one of the canonical vartype representations of a
// wasm::ValType.
static Type canonicalize(Type t) {
switch (t.which()) {
case Fixnum:
case Signed:
case Unsigned:
case Int:
return Int;
case Float:
return Float;
case DoubleLit:
case Double:
return Double;
case Void:
return Void;
case MaybeDouble:
case MaybeFloat:
case Floatish:
case Intish:
// These types need some kind of coercion, they can't be mapped
// to an VarType.
break;
}
MOZ_CRASH("Invalid vartype");
}
Which which() const { return which_; }
bool operator==(Type rhs) const { return which_ == rhs.which_; }
bool operator!=(Type rhs) const { return which_ != rhs.which_; }
bool operator<=(Type rhs) const {
switch (rhs.which_) {
case Signed:
return isSigned();
case Unsigned:
return isUnsigned();
case DoubleLit:
return isDoubleLit();
case Double:
return isDouble();
case Float:
return isFloat();
case MaybeDouble:
return isMaybeDouble();
case MaybeFloat:
return isMaybeFloat();
case Floatish:
return isFloatish();
case Int:
return isInt();
case Intish:
return isIntish();
case Fixnum:
return isFixnum();
case Void:
return isVoid();
}
MOZ_CRASH("unexpected rhs type");
}
bool isFixnum() const { return which_ == Fixnum; }
bool isSigned() const { return which_ == Signed || which_ == Fixnum; }
bool isUnsigned() const { return which_ == Unsigned || which_ == Fixnum; }
bool isInt() const { return isSigned() || isUnsigned() || which_ == Int; }
bool isIntish() const { return isInt() || which_ == Intish; }
bool isDoubleLit() const { return which_ == DoubleLit; }
bool isDouble() const { return isDoubleLit() || which_ == Double; }
bool isMaybeDouble() const { return isDouble() || which_ == MaybeDouble; }
bool isFloat() const { return which_ == Float; }
bool isMaybeFloat() const { return isFloat() || which_ == MaybeFloat; }
bool isFloatish() const { return isMaybeFloat() || which_ == Floatish; }
bool isVoid() const { return which_ == Void; }
bool isExtern() const { return isDouble() || isSigned(); }
// Check if this is one of the valid types for a function argument.
bool isArgType() const { return isInt() || isFloat() || isDouble(); }
// Check if this is one of the valid types for a function return value.
bool isReturnType() const {
return isSigned() || isFloat() || isDouble() || isVoid();
}
// Check if this is one of the valid types for a global variable.
bool isGlobalVarType() const { return isArgType(); }
// Check if this is one of the canonical vartype representations of a
// wasm::ValType, or is void. See Type::canonicalize().
bool isCanonical() const {
switch (which()) {
case Int:
case Float:
case Double:
case Void:
return true;
default:
return false;
}
}
// Check if this is a canonical representation of a wasm::ValType.
bool isCanonicalValType() const { return !isVoid() && isCanonical(); }
// Convert this canonical type to a wasm::ValType.
ValType canonicalToValType() const {
switch (which()) {
case Int:
return ValType::I32;
case Float:
return ValType::F32;
case Double:
return ValType::F64;
default:
MOZ_CRASH("Need canonical type");
}
}
Maybe<ValType> canonicalToReturnType() const {
return isVoid() ? Nothing() : Some(canonicalToValType());
}
// Convert this type to a wasm::TypeCode for use in a wasm
// block signature. This works for all types, including non-canonical
// ones. Consequently, the type isn't valid for subsequent asm.js
// validation; it's only valid for use in producing wasm.
TypeCode toWasmBlockSignatureType() const {
switch (which()) {
case Fixnum:
case Signed:
case Unsigned:
case Int:
case Intish:
return TypeCode::I32;
case Float:
case MaybeFloat:
case Floatish:
return TypeCode::F32;
case DoubleLit:
case Double:
case MaybeDouble:
return TypeCode::F64;
case Void:
return TypeCode::BlockVoid;
}
MOZ_CRASH("Invalid Type");
}
const char* toChars() const {
switch (which_) {
case Double:
return "double";
case DoubleLit:
return "doublelit";
case MaybeDouble:
return "double?";
case Float:
return "float";
case Floatish:
return "floatish";
case MaybeFloat:
return "float?";
case Fixnum:
return "fixnum";
case Int:
return "int";
case Signed:
return "signed";
case Unsigned:
return "unsigned";
case Intish:
return "intish";
case Void:
return "void";
}
MOZ_CRASH("Invalid Type");
}
};
static const unsigned VALIDATION_LIFO_DEFAULT_CHUNK_SIZE = 4 * 1024;
class MOZ_STACK_CLASS ModuleValidatorShared {
public:
struct Memory {
MemoryUsage usage;
uint64_t minLength;
uint64_t minPages() const { return DivideRoundingUp(minLength, PageSize); }
Memory() = default;
};
class Func {
TaggedParserAtomIndex name_;
uint32_t sigIndex_;
uint32_t firstUse_;
uint32_t funcDefIndex_;
bool defined_;
// Available when defined:
uint32_t srcBegin_;
uint32_t srcEnd_;
uint32_t line_;
Bytes bytes_;
Uint32Vector callSiteLineNums_;
public:
Func(TaggedParserAtomIndex name, uint32_t sigIndex, uint32_t firstUse,
uint32_t funcDefIndex)
: name_(name),
sigIndex_(sigIndex),
firstUse_(firstUse),
funcDefIndex_(funcDefIndex),
defined_(false),
srcBegin_(0),
srcEnd_(0),
line_(0) {}
TaggedParserAtomIndex name() const { return name_; }
uint32_t sigIndex() const { return sigIndex_; }
uint32_t firstUse() const { return firstUse_; }
bool defined() const { return defined_; }
uint32_t funcDefIndex() const { return funcDefIndex_; }
void define(ParseNode* fn, uint32_t line, Bytes&& bytes,
Uint32Vector&& callSiteLineNums) {
MOZ_ASSERT(!defined_);
defined_ = true;
srcBegin_ = fn->pn_pos.begin;
srcEnd_ = fn->pn_pos.end;
line_ = line;
bytes_ = std::move(bytes);
callSiteLineNums_ = std::move(callSiteLineNums);
}
uint32_t srcBegin() const {
MOZ_ASSERT(defined_);
return srcBegin_;
}
uint32_t srcEnd() const {
MOZ_ASSERT(defined_);
return srcEnd_;
}
uint32_t line() const {
MOZ_ASSERT(defined_);
return line_;
}
const Bytes& bytes() const {
MOZ_ASSERT(defined_);
return bytes_;
}
Uint32Vector& callSiteLineNums() {
MOZ_ASSERT(defined_);
return callSiteLineNums_;
}
};
using ConstFuncVector = Vector<const Func*>;
using FuncVector = Vector<Func>;
class Table {
uint32_t sigIndex_;
TaggedParserAtomIndex name_;
uint32_t firstUse_;
uint32_t mask_;
bool defined_;
Table(Table&& rhs) = delete;
public:
Table(uint32_t sigIndex, TaggedParserAtomIndex name, uint32_t firstUse,
uint32_t mask)
: sigIndex_(sigIndex),
name_(name),
firstUse_(firstUse),
mask_(mask),
defined_(false) {}
uint32_t sigIndex() const { return sigIndex_; }
TaggedParserAtomIndex name() const { return name_; }
uint32_t firstUse() const { return firstUse_; }
unsigned mask() const { return mask_; }
bool defined() const { return defined_; }
void define() {
MOZ_ASSERT(!defined_);
defined_ = true;
}
};
using TableVector = Vector<Table*>;
class Global {
public:
enum Which {
Variable,
ConstantLiteral,
ConstantImport,
Function,
Table,
FFI,
ArrayView,
ArrayViewCtor,
MathBuiltinFunction
};
private:
Which which_;
union U {
struct VarOrConst {
Type::Which type_;
unsigned index_;
NumLit literalValue_;
VarOrConst(unsigned index, const NumLit& lit)
: type_(Type::lit(lit).which()),
index_(index),
literalValue_(lit) // copies |lit|
{}
VarOrConst(unsigned index, Type::Which which)
: type_(which), index_(index) {
// The |literalValue_| field remains unused and
// uninitialized for non-constant variables.
}
explicit VarOrConst(double constant)
: type_(Type::Double),
literalValue_(NumLit::Double, DoubleValue(constant)) {
// The index_ field is unused and uninitialized for
// constant doubles.
}
} varOrConst;
uint32_t funcDefIndex_;
uint32_t tableIndex_;
uint32_t ffiIndex_;
Scalar::Type viewType_;
AsmJSMathBuiltinFunction mathBuiltinFunc_;
// |varOrConst|, through |varOrConst.literalValue_|, has a
// non-trivial constructor and therefore MUST be placement-new'd
// into existence.
MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS
U() : funcDefIndex_(0) {}
MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS
} u;
friend class ModuleValidatorShared;
template <typename Unit>
friend class ModuleValidator;
friend class js::LifoAlloc;
explicit Global(Which which) : which_(which) {}
public:
Which which() const { return which_; }
Type varOrConstType() const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantLiteral ||
which_ == ConstantImport);
return u.varOrConst.type_;
}
unsigned varOrConstIndex() const {
MOZ_ASSERT(which_ == Variable || which_ == ConstantImport);
return u.varOrConst.index_;
}
bool isConst() const {
return which_ == ConstantLiteral || which_ == ConstantImport;
}
NumLit constLiteralValue() const {
MOZ_ASSERT(which_ == ConstantLiteral);
return u.varOrConst.literalValue_;
}
uint32_t funcDefIndex() const {
MOZ_ASSERT(which_ == Function);
return u.funcDefIndex_;
}
uint32_t tableIndex() const {
MOZ_ASSERT(which_ == Table);
return u.tableIndex_;
}
unsigned ffiIndex() const {
MOZ_ASSERT(which_ == FFI);
return u.ffiIndex_;
}
Scalar::Type viewType() const {
MOZ_ASSERT(which_ == ArrayView || which_ == ArrayViewCtor);
return u.viewType_;
}
bool isMathFunction() const { return which_ == MathBuiltinFunction; }
AsmJSMathBuiltinFunction mathBuiltinFunction() const {
MOZ_ASSERT(which_ == MathBuiltinFunction);
return u.mathBuiltinFunc_;
}
};
struct MathBuiltin {
enum Kind { Function, Constant };
Kind kind;
union {
double cst;
AsmJSMathBuiltinFunction func;
} u;
MathBuiltin() : kind(Kind(-1)), u{} {}
explicit MathBuiltin(double cst) : kind(Constant) { u.cst = cst; }
explicit MathBuiltin(AsmJSMathBuiltinFunction func) : kind(Function) {
u.func = func;
}
};
struct ArrayView {
ArrayView(TaggedParserAtomIndex name, Scalar::Type type)
: name(name), type(type) {}
TaggedParserAtomIndex name;
Scalar::Type type;
};
protected:
class HashableSig {
uint32_t sigIndex_;
const TypeContext& types_;
public:
HashableSig(uint32_t sigIndex, const TypeContext& types)
: sigIndex_(sigIndex), types_(types) {}
uint32_t sigIndex() const { return sigIndex_; }
const FuncType& funcType() const { return types_[sigIndex_].funcType(); }
// Implement HashPolicy:
using Lookup = const FuncType&;
static HashNumber hash(Lookup l) { return l.hash(); }
static bool match(HashableSig lhs, Lookup rhs) {
return lhs.funcType() == rhs;
}
};
class NamedSig : public HashableSig {
TaggedParserAtomIndex name_;
public:
NamedSig(TaggedParserAtomIndex name, uint32_t sigIndex,
const TypeContext& types)
: HashableSig(sigIndex, types), name_(name) {}
TaggedParserAtomIndex name() const { return name_; }
// Implement HashPolicy:
struct Lookup {
TaggedParserAtomIndex name;
const FuncType& funcType;
Lookup(TaggedParserAtomIndex name, const FuncType& funcType)
: name(name), funcType(funcType) {}
};
static HashNumber hash(Lookup l) {
return HashGeneric(TaggedParserAtomIndexHasher::hash(l.name),
l.funcType.hash());
}
static bool match(NamedSig lhs, Lookup rhs) {
return lhs.name() == rhs.name && lhs.funcType() == rhs.funcType;
}
};
using SigSet = HashSet<HashableSig, HashableSig>;
using FuncImportMap = HashMap<NamedSig, uint32_t, NamedSig>;
using GlobalMap =
HashMap<TaggedParserAtomIndex, Global*, TaggedParserAtomIndexHasher>;
using MathNameMap =
HashMap<TaggedParserAtomIndex, MathBuiltin, TaggedParserAtomIndexHasher>;
using ArrayViewVector = Vector<ArrayView>;
protected:
JSContext* cx_;
ParserAtomsTable& parserAtoms_;
FunctionNode* moduleFunctionNode_;
TaggedParserAtomIndex moduleFunctionName_;
TaggedParserAtomIndex globalArgumentName_;
TaggedParserAtomIndex importArgumentName_;
TaggedParserAtomIndex bufferArgumentName_;
MathNameMap standardLibraryMathNames_;
// Validation-internal state:
LifoAlloc validationLifo_;
Memory memory_;
FuncVector funcDefs_;
TableVector tables_;
GlobalMap globalMap_;
SigSet sigSet_;
FuncImportMap funcImportMap_;
ArrayViewVector arrayViews_;
// State used to build the AsmJSModule in finish():
CompilerEnvironment compilerEnv_;
ModuleEnvironment moduleEnv_;
MutableAsmJSMetadata asmJSMetadata_;
// Error reporting:
UniqueChars errorString_ = nullptr;
uint32_t errorOffset_ = UINT32_MAX;
bool errorOverRecursed_ = false;
protected:
ModuleValidatorShared(JSContext* cx, ParserAtomsTable& parserAtoms,
FunctionNode* moduleFunctionNode)
: cx_(cx),
parserAtoms_(parserAtoms),
moduleFunctionNode_(moduleFunctionNode),
moduleFunctionName_(FunctionName(moduleFunctionNode)),
standardLibraryMathNames_(cx),
validationLifo_(VALIDATION_LIFO_DEFAULT_CHUNK_SIZE),
funcDefs_(cx),
tables_(cx),
globalMap_(cx),
sigSet_(cx),
funcImportMap_(cx),
arrayViews_(cx),
compilerEnv_(CompileMode::Once, Tier::Optimized, OptimizedBackend::Ion,
DebugEnabled::False),
moduleEnv_(FeatureArgs(), ModuleKind::AsmJS) {
compilerEnv_.computeParameters();
memory_.minLength = RoundUpToNextValidAsmJSHeapLength(0);
}
protected:
[[nodiscard]] bool initModuleEnvironment() { return moduleEnv_.initTypes(0); }
[[nodiscard]] bool addStandardLibraryMathInfo() {
static constexpr struct {
const char* name;
AsmJSMathBuiltinFunction func;
} functions[] = {
{"sin", AsmJSMathBuiltin_sin}, {"cos", AsmJSMathBuiltin_cos},
{"tan", AsmJSMathBuiltin_tan}, {"asin", AsmJSMathBuiltin_asin},
{"acos", AsmJSMathBuiltin_acos}, {"atan", AsmJSMathBuiltin_atan},
{"ceil", AsmJSMathBuiltin_ceil}, {"floor", AsmJSMathBuiltin_floor},
{"exp", AsmJSMathBuiltin_exp}, {"log", AsmJSMathBuiltin_log},
{"pow", AsmJSMathBuiltin_pow}, {"sqrt", AsmJSMathBuiltin_sqrt},
{"abs", AsmJSMathBuiltin_abs}, {"atan2", AsmJSMathBuiltin_atan2},
{"imul", AsmJSMathBuiltin_imul}, {"clz32", AsmJSMathBuiltin_clz32},
{"fround", AsmJSMathBuiltin_fround}, {"min", AsmJSMathBuiltin_min},
{"max", AsmJSMathBuiltin_max},
};
auto AddMathFunction = [this](const char* name,
AsmJSMathBuiltinFunction func) {
auto atom = parserAtoms_.internAscii(cx_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(func);
return this->standardLibraryMathNames_.putNew(atom, builtin);
};
for (const auto& info : functions) {
if (!AddMathFunction(info.name, info.func)) {
return false;
}
}
static constexpr struct {
const char* name;
double value;
} constants[] = {
{"E", M_E},
{"LN10", M_LN10},
{"LN2", M_LN2},
{"LOG2E", M_LOG2E},
{"LOG10E", M_LOG10E},
{"PI", M_PI},
{"SQRT1_2", M_SQRT1_2},
{"SQRT2", M_SQRT2},
};
auto AddMathConstant = [this](const char* name, double cst) {
auto atom = parserAtoms_.internAscii(cx_, name, strlen(name));
if (!atom) {
return false;
}
MathBuiltin builtin(cst);
return this->standardLibraryMathNames_.putNew(atom, builtin);
};
for (const auto& info : constants) {
if (!AddMathConstant(info.name, info.value)) {
return false;
}
}
return true;
}
public:
JSContext* cx() const { return cx_; }
TaggedParserAtomIndex moduleFunctionName() const {
return moduleFunctionName_;
}
TaggedParserAtomIndex globalArgumentName() const {
return globalArgumentName_;
}
TaggedParserAtomIndex importArgumentName() const {
return importArgumentName_;
}
TaggedParserAtomIndex bufferArgumentName() const {
return bufferArgumentName_;
}
const ModuleEnvironment& env() { return moduleEnv_; }
void initModuleFunctionName(TaggedParserAtomIndex name) {
MOZ_ASSERT(!moduleFunctionName_);
moduleFunctionName_ = name;
}
[[nodiscard]] bool initGlobalArgumentName(TaggedParserAtomIndex n) {
globalArgumentName_ = n;
if (n) {
asmJSMetadata_->globalArgumentName = parserAtoms_.toNewUTF8CharsZ(cx_, n);
if (!asmJSMetadata_->globalArgumentName) {
return false;
}
}
return true;
}
[[nodiscard]] bool initImportArgumentName(TaggedParserAtomIndex n) {
importArgumentName_ = n;
if (n) {
asmJSMetadata_->importArgumentName = parserAtoms_.toNewUTF8CharsZ(cx_, n);
if (!asmJSMetadata_->importArgumentName) {
return false;
}
}
return true;
}
[[nodiscard]] bool initBufferArgumentName(TaggedParserAtomIndex n) {
bufferArgumentName_ = n;
if (n) {
asmJSMetadata_->bufferArgumentName = parserAtoms_.toNewUTF8CharsZ(cx_, n);
if (!asmJSMetadata_->bufferArgumentName) {
return false;